pax_global_header00006660000000000000000000000064134155414220014513gustar00rootroot0000000000000052 comment=3fb20e61c2058676d3b973dc9aefdc8ca0065e05 gnome-shell-extension-gsconnect-20/000077500000000000000000000000001341554142200175035ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/.eslintrc.json000066400000000000000000000053261341554142200223050ustar00rootroot00000000000000{ "env": { "es6": true }, "extends": "eslint:recommended", "rules": { "array-bracket-newline": [ "error", "consistent" ], "array-bracket-spacing": [ "error", "never" ], "brace-style": "error", "comma-spacing": [ "error", { "before": false, "after": true } ], "indent": [ "error", 4, { "MemberExpression": "off", "SwitchCase": 1 } ], "key-spacing": [ "error", { "beforeColon": false, "afterColon": true } ], "keyword-spacing": [ "error", { "before": true, "after": true } ], "linebreak-style": [ "error", "unix" ], "no-empty": [ "error", { "allowEmptyCatch": true } ], "no-implicit-coercion": [ "error", { "allow": ["!!"] } ], "no-unused-vars": [ "error", { "args": "none", "vars": "local" } ], "object-curly-newline": [ "error", { "consistent": true } ], "object-curly-spacing": "error", "prefer-template": "off", "quotes": [ "error", "single", { "avoidEscape": true } ], "semi": [ "error", "always" ], "semi-spacing": [ "error", { "before": false, "after": true } ], "space-before-blocks": "error", "space-infix-ops": [ "error", { "int32Hint": false } ] }, "globals": { "ARGV": false, "Debugger": false, "GIRepositoryGType": false, "imports": false, "Intl": false, "log": false, "logError": false, "print": false, "printerr": false, "window": false, "global": false, "gsconnect": false, "debug": false, "warning": false, "_": false, "_C": false, "_N": false, "ngettext": false, "_GSOUND_CONTEXT": false, "_SFX_BACKEND": false, "_WAYLAND": false }, "parserOptions": { "ecmaVersion": 2017 } } gnome-shell-extension-gsconnect-20/.github/000077500000000000000000000000001341554142200210435ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/.github/ISSUE_TEMPLATE/000077500000000000000000000000001341554142200232265ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000026171341554142200257260ustar00rootroot00000000000000--- name: Bug report about: Report an issue to help us improve GSConnect labels: --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. You can drag-and-drop or cut-and-paste images directly into this edit window, to include them in your report. **Debug output** If you have been able to reproduce the problem with debugging enabled (see [instructions in the wiki](wiki/Debugging)), paste any log messages related to this issue between the two ``` lines below. ``` ``` **System Details (please complete the following information):** - GSConnect version: [e.g. 16, 15, ... (displayed in the GSConnect "About" window)] - Installed from: [e.g. extensions.gnome.org, GitHub Release download, distro package, ...] - GNOME/Shell version: [e.g. 3.30, 3.28, ...] - Distro/Release: [e.g. Ubuntu 18.04, Fedora 29, Arch, ...] **GSConnect environment (if applicable):** - Paired Device(s): [e.g. Galaxy Note 8, Pixel 2, Honor 9, ...] - KDE Connect app version: [e.g. ] - Plugin(s): [if the issue only occurs when using certain plugin(s)] **Additional context** Add any other context about the problem here. gnome-shell-extension-gsconnect-20/.github/ISSUE_TEMPLATE/feature-request-or-idea.md000066400000000000000000000027071341554142200302150ustar00rootroot00000000000000--- name: Feature request or idea about: Suggest an improvement to GSConnect labels: --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **System Details (please complete the following information):** - GSConnect version: [e.g. 16, 15, ... (displayed in the GSConnect "About" window)] - Installed from: [e.g. extensions.gnome.org, GitHub Release download, distro package, ...] **Additional context** Add any other context for your suggestion here, including screenshots if applicable. #### Please delete this line and everything below before submitting your request GSConnect works in concert with the [KDE Connect][1] 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. Any 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. [1]: (https://community.kde.org/KDEConnect) gnome-shell-extension-gsconnect-20/.gitignore000066400000000000000000000000121341554142200214640ustar00rootroot00000000000000_build *~ gnome-shell-extension-gsconnect-20/.travis.yml000066400000000000000000000001301341554142200216060ustar00rootroot00000000000000language: node_js node_js: - "node" script: - npm run lint - npm run lint-webext gnome-shell-extension-gsconnect-20/CODE_OF_CONDUCT.md000066400000000000000000000001201341554142200222730ustar00rootroot00000000000000# Code of Conduct Stay on topic. This applies to what you think, say, and do. gnome-shell-extension-gsconnect-20/CONTRIBUTING.md000066400000000000000000000121041341554142200217320ustar00rootroot00000000000000# 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, not just duplicating its functionality. The [GNOME Shell Design Principles][design] and [GNOME Human Interface Guidelines][hig] are followed whenever appropriate. ## Code of Conduct Stay on topic. This applies to what you think, say, and do. ## 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 SHOULD be written in [GJS][gjs] if at all possible Almost all of the GNOME API is available through introspection, however in the few cases it is not Python is acceptable. * Code MUST NOT be written in a pre-compiled language The extension is distributed as-is to users via the GNOME Shell Extensions website and must not include architecture dependant code or binaries. * 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 run on any committed code by the CI and can be run on code simply with `eslint src/`. When in doubt, copy existing style. A short example: ```js // ES6 Syntax and Classes class Foo extends Bar { // 4-space indents and 80-character lines constructor() { } // Properties first, only use GProperties when necessary get baz() { // _private and __implementation prefixes return this._baz; } // Spaces between assigments and use %null instead of %undefined qux(quz = null) { // Guard clauses can be single-line if (!quz) return; // Otherwise, even single statements should be in blocks if (some_conditional) { // camelCase naming, unless sublcassing a GObject doSomething(); } } // Prefer async functions over Promises, if possible async doSomething() { try { throw new Error(); } catch (e) { // Always catch and call `logError()` or manually reject the error return Promise.reject(e); } } } // End files with a newline (\n) ``` ### Developer Tool GSConnect ships with a development tool, although it is now hidden from users due to the confusion it caused. This is not guaranteed to ever work and should not be marked for translation. It can be started with `gapplication`: $ gapplication action org.gnome.Shell.Extensions.GSConnect devel ### A Note About Template Strings JavaScript template literals are not handled well by gettext, like in the case `` `Breaks!` ``. Once you have committed your changes and are about to push, run the meson target for the POT file to confirm it still works: ```sh ninja -C _build/ org.gnome.Shell.Extensions.GSConnect-pot ninja: Entering directory `_build/' [0/1] Running external command org.gnome.Shell.Extensions.GSConnect-pot. src/extension.js:396: warning: RegExp literal terminated too early ``` Note that error messages might be incorrect and the line number earlier in the file than is claimed. Adjust the code to use concatentation with `+` or `join()` until it succeeds. Then discard the POT file changes and amend your commit: ```sh git checkout -- po/org.gnome.Shell.Extensions.GSConnect.pot git commit --amend --no-edit ``` ## 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/andyholmes/gnome-shell-extension-gsconnect/wiki/Translating [packaging]: https://github.com/andyholmes/gnome-shell-extension-gsconnect/wiki/Packaging [theming]: https://github.com/andyholmes/gnome-shell-extension-gsconnect/wiki/Theming [issue]: https://github.com/andyholmes/gnome-shell-extension-gsconnect/issues [pr]: https://github.com/GNOME/gnome-shell/pulls [gjs]: https://gitlab.gnome.org/GNOME/gjs/wikis/home gnome-shell-extension-gsconnect-20/LICENSE000066400000000000000000000431761341554142200205230ustar00rootroot00000000000000 GNU 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. GNU GENERAL PUBLIC LICENSE 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. {description} Copyright (C) {year} {fullname} 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 This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. gnome-shell-extension-gsconnect-20/README.md000066400000000000000000000026731341554142200207720ustar00rootroot00000000000000[GSConnect][ego] is a complete implementation of [KDE Connect][kdeconnect] for GNOME Shell with Nautilus, [Chrome][chrome] and [Firefox][firefox] integration. Pair your PC and your Android device with the KDE Connect app installed to: Transfer files, send and receive SMS texts, mirror clipboard and notifications, send mouse and keyboard input, remote-control media players, locate your device, monitor battery levels, launch commands on your PC, and more. Please see the **[Wiki][wiki]** for more information and **[Help][help]**. [Get it on GNOME Extensions][ego] [Available in the Chrome Web Store][chrome] [Get the Add-On][firefox] [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://community.kde.org/KDEConnect [wiki]: https://github.com/andyholmes/gnome-shell-extension-gsconnect/wiki/ [help]: https://github.com/andyholmes/gnome-shell-extension-gsconnect/wiki/Help gnome-shell-extension-gsconnect-20/crowdin.yml000066400000000000000000000010251341554142200216710ustar00rootroot00000000000000project_identifier: org.gnome.Shell.Extensions.GSConnect files: - source: /po/org.gnome.Shell.Extensions.GSConnect.pot translation: /po/%locale%.po languages_mapping: locale: ca-ES: ca ca: ca fr: fr de: de pt-BR: pt_BR nl: nl_NL nl-BE: nl_BE es-ES: es be: be cs: cs et: et hu: hu it: it ja: ja lt: lt pl: pl ru: ru sk: sk sr: sr tr: tr zh-CN: zh_CN gnome-shell-extension-gsconnect-20/data/000077500000000000000000000000001341554142200204145ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/data/application.css000066400000000000000000000053571341554142200234430ustar00rootroot00000000000000 /** * 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-window { border-bottom: 1px solid @borders; } /** * GSConnectMessagingWindow */ .message-list > label { font-size: small; margin-bottom: 1em; } .conversation-list { background: @content_view_bg_color; } /** * Incoming Message Bubbles (GtkLabel subclass) */ .message-in { background: @theme_selected_bg_color; color: @theme_selected_fg_color; padding: 6px; padding-right: 9px; padding-left: 9px; } .message-in selection { background-color: @theme_selected_fg_color; color: @theme_selected_bg_color; } .message-in:dir(ltr) { border-radius: 0.25em 1em 1em 0.25em; } .message-in:first-child:dir(ltr) { border-top-left-radius: 1em; } .message-in:last-child:dir(ltr) { border-bottom-left-radius: 1em; } .message-in:dir(rtl) { border-radius: 1em 0.25em 0.25em 1em; } .message-in:first-child:dir(rtl) { border-top-right-radius: 1em; } .message-in:last-child:dir(rtl) { border-bottom-right-radius: 1em; } /** * Must contrast with .message-in background */ .message-in *:link { color: @theme_selected_fg_color; } /** * Outgoing Message Bubbles (GtkLabel subclass) */ .message-out { background: alpha(@theme_selected_bg_color, 0.2); caret-color: currentColor; -gtk-secondary-caret-color: currentColor; padding: 6px; padding-right: 6px; padding-left: 9px; } .message-out selection { background-color: @theme_selected_bg_color; color: @theme_selected_fg_color; } .message-out:dir(ltr) { border-right: 3px solid @theme_selected_bg_color; border-radius: 1em 0.25em 0.25em 1em; } .message-out:first-child:dir(ltr) { border-top-right-radius: 1em; } .message-out:last-child:dir(ltr) { border-bottom-right-radius: 1em; } .message-out:dir(rtl) { border-left: 3px solid @theme_selected_bg_color; border-radius: 0.25em 1em 1em 0.25em; } .message-out:first-child:dir(rtl) { border-top-left-radius: 1em; } .message-out:last-child:dir(rtl) { border-bottom-left-radius: 1em; } /** * Must contrast with .message-out background */ .message-out *:link { color: @theme_fg_color; } /** * Special case for pending messages */ .message-pending .message-out { opacity: 0.5; } gnome-shell-extension-gsconnect-20/data/chrome-badge.png000066400000000000000000000072621341554142200234460ustar00rootroot00000000000000PNG  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`gnome-shell-extension-gsconnect-20/data/computer-symbolic.svg000066400000000000000000000011171341554142200246120ustar00rootroot00000000000000 gnome-shell-extension-gsconnect-20/data/computer.svg000066400000000000000000000014361341554142200227770ustar00rootroot00000000000000 gnome-shell-extension-gsconnect-20/data/connect.ui000066400000000000000000000207551341554142200224150ustar00rootroot00000000000000 1716 1764 1 1 gnome-shell-extension-gsconnect-20/data/contacts.ui000066400000000000000000000101461341554142200225730ustar00rootroot00000000000000 True False center center True False 144 avatar-default-symbolic 0 0 True False No contacts 0 1 Help True True True none https://github.com/andyholmes/gnome-shell-extension-gsconnect/wiki/Help#contacts-dont-sync 0 2 gnome-shell-extension-gsconnect-20/data/conversation.ui000066400000000000000000000106331341554142200234700ustar00rootroot00000000000000 gnome-shell-extension-gsconnect-20/data/devel.ui000066400000000000000000002267311341554142200220650ustar00rootroot00000000000000 1 2147483647 1 1 1 1 2147483647 2147483647 1 1 gnome-shell-extension-gsconnect-20/data/device.ui000066400000000000000000003747321341554142200222320ustar00rootroot00000000000000 True False center center 16 32 system-run-symbolic gnome-shell-extension-gsconnect-20/data/enter-keyboard-shortcut.svg000066400000000000000000000433331341554142200257270ustar00rootroot00000000000000 image/svg+xml gnome-shell-extension-gsconnect-20/data/firefox-badge.png000066400000000000000000000136561341554142200236370ustar00rootroot00000000000000PNG  IHDR<WQbKGD pHYs  tIME 2g;IDATx{\}?GwO_i4HH0 k1:֚ RIJ*{Uq,»^`,1%+ $$$3iH~9vtfdWu~ݾO:2Q#_)ۧhco_eV?u_SٮA˾U# x|]v Ǘ$s|=vV? §YD ,c]v)Z`U5_ #5l^RM{m\@c{w_~>w'yESx,y0؍ݮ^<4XڳwpsK%Ϝ`K[m]׎D~d&c"LfS!̀= >c׀2 N; zhYdAy90Q5mֳeugO; FuUjAa#9p>5%48[ٓvv2Ό hdDP/37}I%ƽd<-tj}iJy:yX|YiK[-]Ϝ0zuE95{7vT3Q=>8k˞JO[_f{w l"w;@}jij1V=!T{BCXaO~pVWfM!P'sC0o+neD})jGYLcº OCsNWY^n9͚'@mn_zZ=WkwK$( w=y{ ޜTn7` ?`ͱ.*,"GOE14/>H0yyd=v/xazX<{zV>hov.6K53[gS4&,v$˔&HƁa\@) p_Ґtqّ3h7C,dX#I~tz tox S`dD;_EZvu #H0}Y;ݣݭ%mTzr}"* U\׭eťopj'%[9٠tW!ATBvn gwBhEEI@hMGmv23ӱ^=˶[tC5[V7Ou-hȉ$^[4j*L4Rk ıdv]TXZCg5d'6P+ܑ>h`*rS4bFܺt3 ⁈޿;)i*hZE)48H6/ v96< :fP 2L=̧!;zǚ>>⬆akGjQĪɏ܉:slq[m Dm* ?Wer^6a)dI|7;V7K%RQT$(Gr⼼gO|=:Pf0ӗظV=p'ej\\h19^@?zΌ{ugcPޭXk7TYWb;32ѧVG 6=Hd[=)#COM|e`*'_ |Atر FET3ӟu(^`$җ)+1-/+YRl^*j.:`X;]`iw\" 1Bx H0.ihEm2C ?kJL4ᖥ']֍yLNRkɞm5Ϫ~NXW-Nh7wT'Bv v2ƼBkI]We9ٯd;<ȦiLX4&,]^} pja=o/K;C~KzX2V!hBHz94ġfjT)IiMqd $a+E/?ZwRMfWN حIzS]|g0ln[JF^;>TW$; <8;[͟Wqjz 5 ڹy ;XD0 } n.+ϝE֐xR`F}qXŴrSm4"`}TtUS lO) W{tJЧpoiקe+mSG8??/[l kxE q{qQ06-.fX2a1I"h2̍IQ)LOWNN:9o\>#v_Yթ8\]#%kLJ&M ߛ螑fg=zZ WA㓰gm-k2Sz22gӂFɊor CN<yhدК sl~\{~ڑ,i;12%;OzTUFV`[!dE~йoN#r]%RSX$P ͼQ}'eXN ZKOQ"wC7ڠF Q4;]} ^rY xn-YD6i{4OwT}Y:[ X:V\gjj^.4ql.%_D0-ʨBI=rM{O9`ʐh B/ID !xB3 c v5GKD,1ejmbElC9L?1!aj?L Tas"Jd]{٫%dת弻d z{Ys?4gH QVfn!7C`d"њnZ̛sWqa+Z s|B/hYaFh)=LMcj%l*O[Yd=XmZ;WW0-Eo"9d_`GpaJ6x˻Qk|=v۶C/{ N- U?ud3'x:S-kvٿ@ZIENDB`gnome-shell-extension-gsconnect-20/data/gsconnect.xml000066400000000000000000000003251341554142200231210ustar00rootroot00000000000000 GSConnect GSConnect service gnome-shell-extension-gsconnect-20/data/laptop-symbolic.svg000066400000000000000000000015051341554142200242540ustar00rootroot00000000000000 gnome-shell-extension-gsconnect-20/data/laptop.svg000066400000000000000000000016411341554142200224360ustar00rootroot00000000000000 gnome-shell-extension-gsconnect-20/data/menus.ui000066400000000000000000000142661341554142200221130ustar00rootroot00000000000000
Connect to… win.connect
Display Mode Panel win.display-mode panel User Menu win.display-mode user-menu
Generate Support Log win.support-log Help app.wiki Help About win.about
Switch to Bluetooth settings.connect-bluetooth bluetooth-symbolic action-disabled Switch to LAN settings.connect-tcp network-wireless-symbolic action-disabled 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 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 Lower audio-volume-low-symbolic settings.talking-volume lower Mute audio-volume-muted-symbolic settings.talking-volume mute
gnome-shell-extension-gsconnect-20/data/messaging.ui000066400000000000000000000274131341554142200227370ustar00rootroot00000000000000 True False center center True False 96 view-list-symbolic 0 0 Help True True True none https://github.com/andyholmes/gnome-shell-extension-gsconnect/wiki/Help#sms-messages-dont-sync 0 2 True False 12 No Conversations 0 1 gnome-shell-extension-gsconnect-20/data/metadata.json.in000066400000000000000000000021741341554142200235000ustar00rootroot00000000000000{ "extension-id": "gsconnect", "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 and Windows.\n\nKDE Connect Indicator can support Gtk desktops other than GNOME Shell.\n\nPlease report issues on Github!", "version": @VERSION@, "shell-version": [ "3.28", "3.30" ], "url": "https://github.com/andyholmes/gnome-shell-extension-gsconnect/wiki", "libdir": "@GNOME_SHELL_LIBDIR@", "localedir": "@LOCALEDIR@", "gschemadir": "@GSCHEMADIR@", "bin": { "fusermount": "@FUSERMOUNT_PATH@", "openssl": "@OPENSSL_PATH@", "ssh_add": "@SSHADD_PATH@", "ssh_keygen": "@SSHKEYGEN_PATH@", "sshfs": "@SSHFS_PATH@" } } gnome-shell-extension-gsconnect-20/data/notification.ui000066400000000000000000000202131341554142200234370ustar00rootroot00000000000000 gnome-shell-extension-gsconnect-20/data/org.gnome.Shell.Extensions.GSConnect-symbolic.svg000066400000000000000000000005211341554142200316730ustar00rootroot00000000000000 gnome-shell-extension-gsconnect-20/data/org.gnome.Shell.Extensions.GSConnect.desktop000066400000000000000000000004661341554142200307360ustar00rootroot00000000000000[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 gnome-shell-extension-gsconnect-20/data/org.gnome.Shell.Extensions.GSConnect.gresource.xml000066400000000000000000000060511341554142200320560ustar00rootroot00000000000000 application.css connect.ui contacts.ui conversation.ui devel.ui device.ui messaging.ui notification.ui settings.ui telephony.ui enter-keyboard-shortcut.svg org.gnome.Shell.Extensions.GSConnect.desktop org.gnome.Shell.Extensions.GSConnect.service org.gnome.Shell.Extensions.GSConnect.xml org.gnome.Shell.Extensions.GSConnect.sdp.xml org.gnome.shell.extensions.gsconnect.json-chrome org.gnome.shell.extensions.gsconnect.json-mozilla org.gnome.Shell.Extensions.GSConnect.svg org.gnome.Shell.Extensions.GSConnect-symbolic.svg sms-send.svg sms-symbolic.svg computer.svg computer-symbolic.svg laptop.svg laptop-symbolic.svg smartphone.svg smartphone-symbolic.svg tablet.svg tablet-symbolic.svg firefox-badge.png chrome-badge.png menus.ui gnome-shell-extension-gsconnect-20/data/org.gnome.Shell.Extensions.GSConnect.gschema.xml000066400000000000000000000125171341554142200314730ustar00rootroot00000000000000 false "GSConnect" [] 0 false true (0, 0) (300, 300) false (0, 0) (640, 440) false "" "" {} ["sms", "ring", "mount", "commands", "share"] "" false "unknown" [] [] [] [] [] "" 1716 "" "" "" false false false true true true true '{}' {} false true "lower" false "mute" true true gnome-shell-extension-gsconnect-20/data/org.gnome.Shell.Extensions.GSConnect.sdp.xml000066400000000000000000000020521341554142200306430ustar00rootroot00000000000000 gnome-shell-extension-gsconnect-20/data/org.gnome.Shell.Extensions.GSConnect.service000066400000000000000000000001361341554142200307170ustar00rootroot00000000000000[D-BUS Service] Name=org.gnome.Shell.Extensions.GSConnect Exec=@EXTDATADIR@/service/daemon.js gnome-shell-extension-gsconnect-20/data/org.gnome.Shell.Extensions.GSConnect.svg000066400000000000000000000010621341554142200300550ustar00rootroot00000000000000 gnome-shell-extension-gsconnect-20/data/org.gnome.Shell.Extensions.GSConnect.xml000066400000000000000000000052201341554142200300560ustar00rootroot00000000000000 gnome-shell-extension-gsconnect-20/data/org.gnome.shell.extensions.gsconnect.json-chrome000066400000000000000000000004411341554142200317420ustar00rootroot00000000000000{ "name": "org.gnome.shell.extensions.gsconnect", "description": "Native messaging host for GSConnect WebExtension", "path": "@EXTDATADIR@/service/nativeMessagingHost.js", "type": "stdio", "allowed_origins": [ "chrome-extension://jfnifeihccihocjbfcfhicmmgpjicaec/" ] } gnome-shell-extension-gsconnect-20/data/org.gnome.shell.extensions.gsconnect.json-mozilla000066400000000000000000000004161341554142200321360ustar00rootroot00000000000000{ "name": "org.gnome.shell.extensions.gsconnect", "description": "Native messaging host for GSConnect WebExtension", "path": "@EXTDATADIR@/service/nativeMessagingHost.js", "type": "stdio", "allowed_extensions": [ "gsconnect@andyholmes.github.io" ] } gnome-shell-extension-gsconnect-20/data/settings.ui000066400000000000000000001101201341554142200226060ustar00rootroot00000000000000 52 True False True Searching for devices… False True True True center center 6 6 6 6 True name gnome-shell-extension-gsconnect-20/data/smartphone-symbolic.svg000066400000000000000000000005071341554142200251360ustar00rootroot00000000000000 gnome-shell-extension-gsconnect-20/data/smartphone.svg000066400000000000000000000034601341554142200233200ustar00rootroot00000000000000 gnome-shell-extension-gsconnect-20/data/sms-send.svg000066400000000000000000000003001341554142200226570ustar00rootroot00000000000000 gnome-shell-extension-gsconnect-20/data/sms-symbolic.svg000066400000000000000000000011721341554142200235570ustar00rootroot00000000000000 gnome-shell-extension-gsconnect-20/data/tablet-symbolic.svg000066400000000000000000000007531341554142200242340ustar00rootroot00000000000000 gnome-shell-extension-gsconnect-20/data/tablet.svg000066400000000000000000000036701341554142200224160ustar00rootroot00000000000000 gnome-shell-extension-gsconnect-20/data/telephony.ui000066400000000000000000000154041341554142200227660ustar00rootroot00000000000000 gnome-shell-extension-gsconnect-20/meson.build000066400000000000000000000123501341554142200216460ustar00rootroot00000000000000project('gsconnect', 'c', version: '20', meson_version: '>= 0.45.0') gnome = import('gnome') prefix = get_option('prefix') datadir = join_paths(prefix, get_option('datadir')) libdir = join_paths(prefix, get_option('libdir')) localedir = join_paths(prefix, get_option('localedir')) sysconfdir = get_option('sysconfdir') extuuid = 'gsconnect@andyholmes.github.io' extdatadir = join_paths(datadir, 'gnome-shell', 'extensions', extuuid) # GSettings schema dir if get_option('gsettings_schemadir') != '' gschemadir = get_option('gsettings_schemadir') else gschemadir = join_paths(datadir, 'glib-2.0', 'schemas') endif # 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('VERSION', meson.project_version()) extconfig.set('GNOME_SHELL_LIBDIR', gnome_shell_libdir) extconfig.set('LOCALEDIR', localedir) extconfig.set('EXTDATADIR', extdatadir) extconfig.set('GSCHEMADIR', gschemadir) extconfig.set('FUSERMOUNT_PATH', get_option('fusermount_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')) extconfig.set('SSHFS_PATH', get_option('sshfs_path')) # ZIP targets for user extension builds run_target( 'make-zip', command: [ find_program('env'), 'UUID=' + extuuid, 'DATADIR=' + datadir, 'LOCALEDIR=' + localedir, 'GSCHEMADIR=' + gschemadir, 'meson/mkzip.sh' ] ) run_target( 'make-sfx', command: [ find_program('env'), 'UUID=' + extuuid, 'DATADIR=' + datadir, 'LOCALEDIR=' + localedir, 'GSCHEMADIR=' + gschemadir, 'SFX=true', 'meson/mkzip.sh' ] ) run_target( 'install-zip', command: [ find_program('env'), 'UUID=' + extuuid, 'DATADIR=' + datadir, 'LOCALEDIR=' + localedir, 'GSCHEMADIR=' + gschemadir, 'INSTALL=true', 'meson/mkzip.sh' ] ) run_target( 'install-sfx', command: [ find_program('env'), 'UUID=' + extuuid, 'DATADIR=' + datadir, 'LOCALEDIR=' + localedir, 'GSCHEMADIR=' + gschemadir, 'INSTALL=true', 'SFX=true', 'meson/mkzip.sh' ] ) # Extension Source install_subdir( 'src', install_dir: extdatadir, strip_directory: true ) # metadata.json configure_file( input: 'data/metadata.json.in', output: 'metadata.json', configuration: extconfig, install_dir: extdatadir ) # Desktop Entry install_data( 'data/org.gnome.Shell.Extensions.GSConnect.desktop', install_dir: join_paths(datadir, 'applications') ) # 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_pkgconfig_variable('session_bus_services_dir') else dbus_dir = join_paths(datadir, 'dbus-1', 'services') endif configure_file( input: 'data/org.gnome.Shell.Extensions.GSConnect.service', output: 'org.gnome.Shell.Extensions.GSConnect.service', configuration: extconfig, install_dir: dbus_dir ) # Nautilus Extension if get_option('nautilus') install_data( 'src/nautilus-gsconnect.py', install_dir: join_paths(datadir, 'nautilus-python', 'extensions') ) endif # WebExtension Manifests if get_option('webextension') # Chrome if get_option('chrome_nmhdir') != '' chrome_nmhdir = get_option('chrome_nmhdir') else chrome_nmhdir = join_paths(sysconfdir, 'opt', 'chrome', 'native-messaging-hosts') endif # Chromium if get_option('chromium_nmhdir') != '' chromium_nmhdir = get_option('chromium_nmhdir') else chromium_nmhdir = join_paths(sysconfdir, 'chromium', 'native-messaging-hosts') endif configure_file( input: 'data/org.gnome.shell.extensions.gsconnect.json-chrome', output: 'org.gnome.shell.extensions.gsconnect.json-chrome', configuration: extconfig ) # HACK: use 'rename' in meson >=0.46.0 meson.add_install_script( 'meson/nmh.sh', join_paths(chrome_nmhdir), join_paths(chromium_nmhdir) ) # Mozilla if get_option('mozilla_nmhdir') != '' mozilla_nmhdir = get_option('mozilla_nmhdir') else mozilla_nmhdir = join_paths(libdir, 'mozilla', 'native-messaging-hosts') endif configure_file( input: 'data/org.gnome.shell.extensions.gsconnect.json-mozilla', output: 'org.gnome.shell.extensions.gsconnect.json', configuration: extconfig, install_dir: mozilla_nmhdir ) endif # GSettings install_data( 'data/org.gnome.Shell.Extensions.GSConnect.gschema.xml', install_dir: gschemadir ) # GResource gnome.compile_resources( 'org.gnome.Shell.Extensions.GSConnect', 'data/org.gnome.Shell.Extensions.GSConnect.gresource.xml', source_dir: 'data', gresource_bundle: true, install: true, install_dir: extdatadir ) # Gettext Translations subdir('po') # Post-Install Script for distributions without the hooks if get_option('post_install') meson.add_install_script( find_program('env').path(), 'GSCHEMADIR=' + gschemadir, join_paths(meson.source_root(), 'meson/post-install.sh') ) endif gnome-shell-extension-gsconnect-20/meson/000077500000000000000000000000001341554142200206245ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/meson/mkzip.sh000077500000000000000000000034541341554142200223230ustar00rootroot00000000000000#!/bin/sh ZIP_DESTDIR="${MESON_BUILD_ROOT}/_zip" ZIP_DIR="${MESON_BUILD_ROOT}/${UUID}" ZIP_FILE="${MESON_BUILD_ROOT}/${UUID}.zip" SFX_FILE="${MESON_BUILD_ROOT}/${UUID}.sfx" GSCHEMA_DIR="${ZIP_DESTDIR}/${GSCHEMADIR}" LOCALE_DIR="${ZIP_DESTDIR}/${LOCALEDIR}" # PRE-CLEAN rm -rf ${ZIP_DESTDIR} ${ZIP_DIR} ${ZIP_FILE} # BUILD cd ${MESON_BUILD_ROOT} DESTDIR=${ZIP_DESTDIR} ninja install # COPY mkdir -p ${ZIP_DIR} cp -pr ${ZIP_DESTDIR}/${DATADIR}/gnome-shell/extensions/${UUID}/* ${ZIP_DIR} cp -pr ${GSCHEMA_DIR} ${ZIP_DIR} glib-compile-schemas ${ZIP_DIR}/schemas if [ -d ${LOCALE_DIR} ]; then cp -pr ${LOCALE_DIR} ${ZIP_DIR} fi # COMPRESS cd ${ZIP_DIR} zip -qr ${ZIP_FILE} . cd ${MESON_BUILD_ROOT} rm -rf _zip echo echo "Extension saved to ${ZIP_FILE}" # SELF EXTRACTOR if [ "$SFX" = true ]; then SFX_CMD="\ #!/bin/sh sed -e '1,/^exit$/d' \"\$0\" > /tmp/gsconnect.zip if [ \"$(sha512sum ${ZIP_FILE} | cut -c -128)\" = \$(sha512sum /tmp/gsconnect.zip | cut -c -128) ]; then printf 'Installing...' mkdir -p ~/.local/share/gnome-shell/extensions rm -rf ~/.local/share/gnome-shell/extensions/gsconnect@andyholmes.github.io unzip /tmp/gsconnect.zip -d ~/.local/share/gnome-shell/extensions/gsconnect@andyholmes.github.io > /dev/null rm /tmp/gsconnect.zip echo 'done' else echo 'Error: Checksum mismatch. Please download the archive again.' fi exit" echo "$SFX_CMD" > "${SFX_FILE}" cat ${ZIP_FILE} >> "${SFX_FILE}" chmod a+x ${SFX_FILE} echo "Installer saved to $SFX_FILE" fi # INSTALL if [ "$INSTALL" = true ]; then EXTENSIONS_DIR="${HOME}/.local/share/gnome-shell/extensions" INSTALL_DIR="${EXTENSIONS_DIR}/${UUID}" mkdir -p ${EXTENSIONS_DIR} rm -rf ${INSTALL_DIR} unzip ${ZIP_FILE} -d ${INSTALL_DIR} echo "Extension installed to $INSTALL_DIR" fi gnome-shell-extension-gsconnect-20/meson/nmh.sh000077500000000000000000000011121341554142200217400ustar00rootroot00000000000000#!/bin/sh # Use DESTDIR if defined if [ -n "${DESTDIR}" ]; then CHROME_NMHDIR=${DESTDIR}/${1} else CHROME_NMHDIR=${1} fi mkdir -p ${CHROME_NMHDIR} cp ${MESON_BUILD_ROOT}/org.gnome.shell.extensions.gsconnect.json-chrome \ ${CHROME_NMHDIR}/org.gnome.shell.extensions.gsconnect.json # Use DESTDIR if defined if [ -n "${DESTDIR}" ]; then CHROMIUM_NMHDIR=${DESTDIR}/${2} else CHROMIUM_NMHDIR=${2} fi mkdir -p ${CHROMIUM_NMHDIR} cp ${MESON_BUILD_ROOT}/org.gnome.shell.extensions.gsconnect.json-chrome \ ${CHROMIUM_NMHDIR}/org.gnome.shell.extensions.gsconnect.json gnome-shell-extension-gsconnect-20/meson/post-install.sh000077500000000000000000000001311341554142200236070ustar00rootroot00000000000000#!/bin/sh GSCHEMA_DIR="${DESTDIR}/${GSCHEMADIR}" glib-compile-schemas "${GSCHEMA_DIR}" gnome-shell-extension-gsconnect-20/meson_options.txt000066400000000000000000000037161341554142200231470ustar00rootroot00000000000000# See https://github.com/andyholmes/gnome-shell-extension-gsconnect/wiki/Packaging # Run meson/post-install.sh (glib-compile-schemas) option( 'post_install', type: 'boolean', value: false, description: 'Run meson/post-install.sh' ) # GNOME Shell LIBDIR option( 'gnome_shell_libdir', type: 'string', value: '', description: 'LIBDIR for GNOME Shell' ) # GSettings schema directory option( 'gsettings_schemadir', type: 'string', value: '', description: 'GSettings Schema directory' ) # DBus service file option( 'session_bus_services_dir', type: 'string', value: '', description: 'DBus session services directory' ) # External program paths option( 'fusermount_path', type: 'string', value: 'fusermount', description: 'Path to fusermount binary' ) option( 'openssl_path', type: 'string', value: 'openssl', description: 'Path to openssl binary' ) option( 'sshadd_path', type: 'string', value: 'ssh-add', description: 'Path to ssh-add binary' ) option( 'sshkeygen_path', type: 'string', value: 'ssh-keygen', description: 'Path to ssh-keygen binary' ) option( 'sshfs_path', type: 'string', value: 'sshfs', description: 'Path to sshfs binary' ) # Enable/Disable Nautilus extension installation option( 'nautilus', type: 'boolean', value: true, description: 'Install nautilus-python extension' ) # Enable/Disable WebExtension manifest installation 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' ) gnome-shell-extension-gsconnect-20/package.json000066400000000000000000000004301341554142200217660ustar00rootroot00000000000000{ "name": "gsconnect", "description": "GSConnect checks", "devDependencies": { "eslint": "^5.8.0" }, "scripts": { "lint": "eslint src/", "lint-webext": "eslint --global 'browser,document,console' webextension/js/background.js webextension/js/popup.js" } } gnome-shell-extension-gsconnect-20/po/000077500000000000000000000000001341554142200201215ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/po/LINGUAS000066400000000000000000000001161341554142200211440ustar00rootroot00000000000000be cs de es et fr hu it ja lt nl_BE nl_NL pl pt_BR ru sk sr sr@latin tr zh_CN gnome-shell-extension-gsconnect-20/po/POTFILES000066400000000000000000000017161341554142200212760ustar00rootroot00000000000000data/connect.ui data/contacts.ui data/conversation.ui data/device.ui data/menus.ui data/messaging.ui data/notification.ui data/settings.ui data/telephony.ui src/extension.js src/nautilus-gsconnect.py src/service/daemon.js src/service/device.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/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/device.js src/service/ui/keybindings.js src/service/ui/messaging.js src/service/ui/service.js src/service/ui/settings.js src/service/ui/telephony.js src/shell/device.js src/shell/donotdisturb.js src/shell/notification.js webextension/gettext.js gnome-shell-extension-gsconnect-20/po/be.po000066400000000000000000000655541341554142200210660ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-12-29 16:44-0800\n" "PO-Revision-Date: 2019-01-08 20:33\n" "Last-Translator: andyholmes \n" "Language-Team: Belarusian\n" "Language: be\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-Generator: crowdin.com\n" "X-Crowdin-Project: gsconnect\n" "X-Crowdin-Language: be\n" "X-Crowdin-File: /master/po/org.gnome.Shell.Extensions.GSConnect.pot\n" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/connect.ui:24 data/menus.ui:7 msgid "Connect to…" msgstr "Падлучыцца да…" #. Action Buttons #: data/connect.ui:30 data/notification.ui:14 data/telephony.ui:14 #: src/service/plugins/share.js:139 src/service/plugins/share.js:310 #: src/service/plugins/share.js:456 src/service/ui/device.js:607 #: src/service/ui/keybindings.js:44 src/service/ui/service.js:37 #: src/service/ui/settings.js:539 src/shell/donotdisturb.js:215 msgid "Cancel" msgstr "Скасаваць" #: data/connect.ui:37 msgid "Connect" msgstr "Злучэнне" #: data/connect.ui:98 msgid "IP Address" msgstr "IP-адрас" #: data/connect.ui:142 msgid "Bluetooth Device" msgstr "Bluetooth прылада" #: data/contacts.ui:49 msgid "Type a phone number or name" msgstr "Увесці нумар тэлефона або імя" #: data/contacts.ui:83 msgid "No contacts" msgstr "Няма кантактаў" #: data/contacts.ui:95 data/menus.ui:33 data/messaging.ui:247 msgid "Help" msgstr "Даведка" #: data/conversation.ui:76 data/conversation.ui:85 src/shell/notification.js:51 msgid "Type a message" msgstr "Напісаць паведамлене" #: data/conversation.ui:84 msgid "Send Message" msgstr "Адправіць паведамленне" #: data/device.ui:67 src/service/plugins/battery.js:12 msgid "Battery" msgstr "Акумулятар" #: data/device.ui:122 msgid "Clipboard Sync" msgstr "Сінхранізацыя буферу абмену" #: data/device.ui:185 msgid "Media Players" msgstr "Медыяплэеры" #: data/device.ui:240 msgid "Mouse & Keyboard" msgstr "Мыш і клавіятура" #: data/device.ui:295 msgid "Volume Control" msgstr "Кіраванне гучнасцю" #: data/device.ui:345 data/device.ui:1728 msgid "Sharing" msgstr "Абагульванне" #: data/device.ui:374 data/device.ui:651 data/device.ui:1774 #: src/service/plugins/runcommand.js:18 src/service/plugins/runcommand.js:169 msgid "Commands" msgstr "Каманды" #: data/device.ui:424 data/device.ui:427 msgid "Name" msgstr "Імя" #: data/device.ui:440 data/device.ui:446 msgid "Command Line" msgstr "Камандны радок" #: data/device.ui:444 data/device.ui:445 msgid "Choose an executable" msgstr "Выбраць выконвальны файл" #: data/device.ui:496 data/device.ui:511 msgid "Add" msgstr "Дадаць" #: data/device.ui:527 data/device.ui:542 msgid "Remove" msgstr "Выдаліць" #: data/device.ui:576 data/device.ui:588 msgid "Edit" msgstr "Рэдагаваць" #: data/device.ui:604 data/device.ui:616 msgid "Save" msgstr "Захаваць" #: data/device.ui:712 msgid "Share Notifications" msgstr "Абагульваць апавяшчэнні" #: data/device.ui:763 msgid "Applications" msgstr "Праграмы" #: data/device.ui:809 data/device.ui:1820 #: src/service/plugins/notification.js:13 msgid "Notifications" msgstr "Апавяшчэнні" #: data/device.ui:839 msgid "Incoming Calls" msgstr "Уваходныя выклікі" #: data/device.ui:888 data/device.ui:1055 msgid "Volume" msgstr "Гук" #: data/device.ui:954 data/device.ui:1120 msgid "Pause Media" msgstr "Прыпыніць плэер" #: data/device.ui:1007 msgid "Ongoing Calls" msgstr "Выходныя выклікі" #: data/device.ui:1176 msgid "Mute Microphone" msgstr "Адключыць мікрафон" #: data/device.ui:1230 data/device.ui:1866 src/service/plugins/telephony.js:13 msgid "Telephony" msgstr "Тэлефанія" #: data/device.ui:1265 msgid "Action Shortcuts" msgstr "Спалучэнні клавіш для дзеянняў" #: data/device.ui:1280 data/device.ui:1349 msgid "Reset All…" msgstr "Скінуць усе…" #: data/device.ui:1334 msgid "Command Shortcuts" msgstr "Камандныя спалучэнні клавіш" #: data/device.ui:1398 msgid "Shortcuts" msgstr "Спалучэнні клавіш" #: data/device.ui:1429 msgid "Plugins" msgstr "Убудовы" #: data/device.ui:1475 msgid "Experimental" msgstr "Эксперыментальныя" #: data/device.ui:1524 msgid "Legacy SMS Support" msgstr "Падтрымка SMS (ранейшая версія)" #: data/device.ui:1598 msgid "Delete" msgstr "Выдаліць" #: data/device.ui:1627 msgid "Delete this device" msgstr "Выдаліць гэту прыладу" #: data/device.ui:1645 msgid "Unpair and remove all settings and files" msgstr "Разлучыць і выдаліць усе налады і файлы" #: data/device.ui:1678 data/device.ui:1958 msgid "Advanced" msgstr "Пашыраныя" #: data/device.ui:1912 msgid "Keyboard Shortcuts" msgstr "Спалучэнні клавіш" #. TRANSLATORS: Send a pair request to the device #: data/device.ui:2015 data/menus.ui:68 src/service/device.js:424 msgid "Pair" msgstr "Спалучыць" #: data/device.ui:2047 msgid "Device is unpaired" msgstr "Прылада разлучана" #: data/device.ui:2062 msgid "You may configure this device before pairing" msgstr "Вы можаце наладзіць гэту прыладу перад спалучэннем" #: data/menus.ui:12 msgid "Display Mode" msgstr "Рэжым паказу" #. TRANSLATORS: Show device indicators in the top bar #: data/menus.ui:15 msgid "Panel" msgstr "Панэль" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/menus.ui:21 msgid "User Menu" msgstr "Меню карыстальніка" #. TRANSLATORS: Generate a support log #: data/menus.ui:29 src/service/ui/settings.js:536 msgid "Generate Support Log" msgstr "Стварыць журнал для падтрымкі" #: data/menus.ui:38 msgid "About" msgstr "Пра нас" #. TRANSLATORS: Change the connection type to Bluetooth #: data/menus.ui:49 msgid "Switch to Bluetooth" msgstr "Пераключыць на Bluetooth" #. TRANSLATORS: Change the connection type to TCP/IP #: data/menus.ui:56 msgid "Switch to LAN" msgstr "Пераключыць на LAN" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/menus.ui:63 src/service/ui/device.js:317 msgid "Encryption Info" msgstr "Інфармацыя пра шыфраванне" #. TRANSLATORS: Unpair the device and notify it #: data/menus.ui:74 src/service/device.js:432 msgid "Unpair" msgstr "Разлучыць" #. TRANSLATORS: Send clipboard content to device #: data/menus.ui:86 msgid "To Device" msgstr "На прыладу" #. TRANSLATORS: Receive clipboard content from the device #: data/menus.ui:92 msgid "From Device" msgstr "З прылады" #. TRANSLATORS: Don't change the system volume #: data/menus.ui:104 data/menus.ui:130 msgid "Nothing" msgstr "Нічога" #. TRANSLATORS: Lower the system volume #: data/menus.ui:111 data/menus.ui:137 msgid "Lower" msgstr "Ніжэйшы" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the phone ringer #: data/menus.ui:118 data/menus.ui:144 src/service/plugins/telephony.js:194 msgid "Mute" msgstr "Выключыць гук" #: data/messaging.ui:12 src/service/plugins/sms.js:26 #: src/service/ui/messaging.js:911 msgid "Messaging" msgstr "Паведамленні" #: data/messaging.ui:21 src/service/ui/messaging.js:992 msgid "New Conversation" msgstr "Новая размова" #: data/messaging.ui:110 msgid "No conversation selected" msgstr "Не выбрана размова" #: data/messaging.ui:126 msgid "Select or start a conversation" msgstr "Выберыце або пачніце размову" #: data/messaging.ui:193 data/notification.ui:52 data/telephony.ui:52 msgid "Device is disconnected" msgstr "Прылада адлучана" #: data/messaging.ui:264 msgid "No Conversations" msgstr "Няма размоў" #: data/notification.ui:21 data/telephony.ui:21 #: src/service/plugins/share.js:457 msgid "Send" msgstr "Адправіць" #: data/settings.ui:10 msgid "Searching for devices…" msgstr "Пошук прылад…" #: data/settings.ui:49 data/settings.ui:63 msgid "Refresh" msgstr "Абнавіць" #: data/settings.ui:74 data/settings.ui:91 src/extension.js:105 msgid "Mobile Settings" msgstr "Налады прылад" #: data/settings.ui:144 data/settings.ui:158 msgid "Edit Device Name" msgstr "Рэдагаваць назву прылады" #: data/settings.ui:236 msgid "Devices" msgstr "Прылады" #: data/settings.ui:299 msgid "Browser Add-Ons" msgstr "Пашырэнні для браўзера" #: data/settings.ui:579 msgid "Enable" msgstr "Уключана" #: data/settings.ui:611 msgid "This device is invisible to unpaired devices" msgstr "Гэта прылада нябачная для разлучаных прылад" #: data/settings.ui:623 src/service/daemon.js:532 msgid "Discovery Disabled" msgstr "Знаходжанне адключана" #. TRANSLATORS: Share URL by SMS #: data/telephony.ui:9 src/service/daemon.js:718 src/service/plugins/sms.js:50 #: webextension/gettext.js:39 msgid "Send SMS" msgstr "Адправіць SMS" #. Service Menu #: src/extension.js:79 src/extension.js:255 msgid "Mobile Devices" msgstr "Мабільныя прылады" #. TRANSLATORS: %d is the number of devices connected #: src/extension.js:253 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d падлучана" msgstr[1] "%d падлучаны" msgstr[2] "%d падлучана" msgstr[3] "" #. TRANSLATORS: Top-level context menu item for GSConnect #: src/nautilus-gsconnect.py:112 webextension/gettext.js:31 msgid "Send To Mobile Device" msgstr "Адправіць на мабільную прыладу" #: src/service/daemon.js:372 msgid "Report" msgstr "Справаздача" #: src/service/daemon.js:507 msgid "Authentication Failure" msgstr "Няўдача праверкі сапраўднасці" #: src/service/daemon.js:516 msgid "Network Error" msgstr "Памылка сеткі" #: src/service/daemon.js:517 src/service/daemon.js:525 msgid "Click for help troubleshooting" msgstr "Націсніце для дапамогі ў выпраўленні непаладак" #: src/service/daemon.js:524 msgid "PulseAudio Error" msgstr "Памылка PulseAudio" #: src/service/daemon.js:533 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "Знаходжанне было адключана праз колькасць прылад у гэтай сетцы." #: src/service/daemon.js:541 #, javascript-format msgid "%s Plugin Failed To Load" msgstr "%s убудова не загрузілася" #: src/service/daemon.js:542 msgid "Click for more information" msgstr "Націсніце для большай інфармацыі" #: src/service/daemon.js:724 msgid "Dial Number" msgstr "Набраць нумар" #: src/service/daemon.js:730 src/service/plugins/share.js:27 msgid "Share File" msgstr "Абагуліць файл" #: src/service/device.js:161 msgid "Not available" msgstr "Недаступны" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:165 #, javascript-format msgid "Bluetooth device at %s" msgstr "Bluetooth прылада па %s" #. TRANSLATORS: Label for TLS Certificate fingerprint #. #. Example: #. #. Google Pixel Fingerprint: #. 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 #: src/service/device.js:183 src/service/device.js:185 #, javascript-format msgid "%s Fingerprint:" msgstr "%s адбітак пальца:" #: src/service/device.js:242 msgid "Laptop" msgstr "Ноўтбук" #: src/service/device.js:244 msgid "Smartphone" msgstr "Смартфон" #: src/service/device.js:246 msgid "Tablet" msgstr "Планшэт" #: src/service/device.js:248 msgid "Desktop" msgstr "Камп'ютар" #: src/service/device.js:408 src/service/ui/device.js:15 msgid "Reconnect" msgstr "Перападлучыцца" #: src/service/device.js:416 src/service/ui/device.js:16 #: src/service/ui/settings.js:371 msgid "Settings" msgstr "Налады" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:607 #, javascript-format msgid "Pair Request from %s" msgstr "Запыт спалучэння ад %s" #: src/service/device.js:614 msgid "Reject" msgstr "Адхіліць" #: src/service/device.js:619 msgid "Accept" msgstr "Прыняць" #: src/service/plugins/battery.js:188 src/service/plugins/findmyphone.js:19 msgid "Ring" msgstr "Званок" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:197 #, javascript-format msgid "%s: Battery is low" msgstr "%s: нізкі зарад акумулятара" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:199 #, javascript-format msgid "%d%% remaining" msgstr "%d%% засталося" #: src/service/plugins/clipboard.js:11 msgid "Clipboard" msgstr "Буфер абмену" #: src/service/plugins/clipboard.js:17 msgid "Clipboard Push" msgstr "Адправіць буфер абмену" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Pull" msgstr "Запрасіць буфер абмену" #: src/service/plugins/contacts.js:12 msgid "Contacts" msgstr "Кантакты" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #: src/service/plugins/contacts.js:263 src/service/plugins/telephony.js:173 #: src/service/plugins/telephony.js:224 src/service/plugins/telephony.js:264 #: src/service/ui/contacts.js:156 src/service/ui/contacts.js:397 msgid "Unknown Contact" msgstr "Невядомы кантакт" #: src/service/plugins/findmyphone.js:13 msgid "Find My Phone" msgstr "Адшукаць мой тэлефон" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Тачпад" #: src/service/plugins/mousepad.js:28 src/service/plugins/mousepad.js:569 msgid "Keyboard" msgstr "Клавіятура" #: src/service/plugins/mousepad.js:223 msgid "Additional Software Required" msgstr "Патрабуецца дадатковае ПЗ" #: src/service/plugins/mousepad.js:586 msgid "Keyboard not ready" msgstr "Клавіятура не гатова" #: src/service/plugins/mpris.js:10 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/notification.js:26 msgid "Cancel Notification" msgstr "Скасаваць апавяшчэнне" #: src/service/plugins/notification.js:34 msgid "Close Notification" msgstr "Закрыць апавяшчэнне" #: src/service/plugins/notification.js:42 msgid "Reply Notification" msgstr "Адказаць на апавяшчэнне" #: src/service/plugins/notification.js:50 msgid "Send Notification" msgstr "Адправіць апавяшчэнне" #: src/service/plugins/ping.js:11 src/service/plugins/ping.js:17 #: src/service/plugins/ping.js:46 msgid "Ping" msgstr "Пінг" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:53 #, javascript-format msgid "Ping: %s" msgstr "Пінг: %s" #: src/service/plugins/runcommand.js:12 msgid "Run Commands" msgstr "Запусціць каманды" #: src/service/plugins/sftp.js:12 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:18 msgid "Mount" msgstr "Падлучыць" #: src/service/plugins/sftp.js:26 msgid "Unmount" msgstr "Адлучыць" #: src/service/plugins/sftp.js:159 msgid "All files" msgstr "Усе файлы" #: src/service/plugins/sftp.js:160 msgid "Camera pictures" msgstr "Фатаграфіі камеры" #: src/service/plugins/sftp.js:417 msgid "Files" msgstr "Файлы" #: src/service/plugins/share.js:13 src/service/plugins/share.js:19 msgid "Share" msgstr "Абагуліць" #: src/service/plugins/share.js:35 msgid "Share Text" msgstr "Абагуліць тэкст" #: src/service/plugins/share.js:43 src/service/ui/messaging.js:975 #: src/service/ui/messaging.js:983 msgid "Share Link" msgstr "Абагуліць спасылку" #: src/service/plugins/share.js:132 src/service/plugins/share.js:303 msgid "Starting Transfer" msgstr "Пачынаецца перадача" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:134 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Атрыманне “%s” з %s" #: src/service/plugins/share.js:172 src/service/plugins/share.js:327 msgid "Transfer Successful" msgstr "Паспяхова перададзена" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:174 #, javascript-format msgid "Received “%s” from %s" msgstr "Атрымана “%s” з %s" #: src/service/plugins/share.js:180 msgid "Open Folder" msgstr "Адкрыць папку" #: src/service/plugins/share.js:185 msgid "Open File" msgstr "Адкрыць файл" #: src/service/plugins/share.js:192 src/service/plugins/share.js:335 msgid "Transfer Failed" msgstr "Збой перадачы" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:194 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Не ўдалося атрымаць “%s” з %s" #: src/service/plugins/share.js:233 #, javascript-format msgid "Text Shared By %s" msgstr "Тэкст абагулены %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:305 #, javascript-format msgid "Sending “%s” to %s" msgstr "Адпраўка “%s” у %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:329 #, 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:337 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "Збой адпраўкі “%s” у %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:408 #, javascript-format msgid "Send files to %s" msgstr "Адправіць файлы ў %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:412 msgid "Open when done" msgstr "Адкрыць па сканчэнні" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:451 #, javascript-format msgid "Send a link to %s" msgstr "Адправіць спасылку ў %s" #: src/service/plugins/sms.js:13 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:34 msgid "New SMS (URI)" msgstr "Новае SMS (URI)" #: src/service/plugins/sms.js:42 src/service/plugins/telephony.js:22 msgid "Reply SMS" msgstr "Адказаць на SMS" #: src/service/plugins/sms.js:58 msgid "Share SMS" msgstr "Абагуліць SMS" #: src/service/plugins/systemvolume.js:11 msgid "System Volume" msgstr "Сістэмная гучнасць" #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Адкл. гук выкліку" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:190 msgid "Incoming call" msgstr "Уваходны выклік" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:205 msgid "Ongoing call" msgstr "Выходны выклік" #. TRANSLATORS: All other phone number types #: src/service/ui/contacts.js:118 src/service/ui/contacts.js:139 #, javascript-format msgid "%s・Other" msgstr "%s・Іншы" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:123 #, javascript-format msgid "%s・Fax" msgstr "%s・Факс" #. TRANSLATORS: A work phone number #: src/service/ui/contacts.js:127 #, javascript-format msgid "%s・Work" msgstr "%s・Работа" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:131 #, javascript-format msgid "%s・Mobile" msgstr "%s・Мабільны" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:135 #, javascript-format msgid "%s・Home" msgstr "%s・Дом" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #: src/service/ui/contacts.js:278 src/service/ui/contacts.js:292 #, javascript-format msgid "Send to %s" msgstr "Адправіць у %s" #: src/service/ui/device.js:608 msgid "Open" msgstr "Адкрыць" #: src/service/ui/device.js:660 src/service/ui/device.js:673 msgid "On" msgstr "Укл." #: src/service/ui/device.js:660 src/service/ui/device.js:673 msgid "Off" msgstr "Выкл." #: src/service/ui/device.js:798 src/service/ui/device.js:826 #: src/service/ui/device.js:850 msgid "Disabled" msgstr "Адключана" #. TRANSLATORS: Title of keyboard shortcut dialog #: src/service/ui/keybindings.js:33 msgid "Set Shortcut" msgstr "Задаць спалучэнні клавіш" #. TRANSLATORS: Button to confirm the new shortcut #: src/service/ui/keybindings.js:47 msgid "Set" msgstr "Задаць" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/service/ui/keybindings.js:54 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Увядзіце новыя спалучэнні, каб змяніць %s" #. TRANSLATORS: Keys for cancelling (␛) or resetting (␈) a shortcut #: src/service/ui/keybindings.js:83 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "Націсніце Esc, каб скасаваць або Прабел, каб скінуць спалучэнне клавіш." #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/service/ui/keybindings.js:182 #, javascript-format msgid "%s is already being used" msgstr "%s ужо выкарыстоўваецца" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:65 src/service/ui/messaging.js:98 msgid "Just now" msgstr "Толькі што" #. TRANSLATORS: Time duration in minutes (eg. 15 minutes) #: src/service/ui/messaging.js:70 src/service/ui/messaging.js:102 #: src/shell/donotdisturb.js:266 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d хвіліна" msgstr[1] "%d хвіліны" msgstr[2] "%d хвілін" msgstr[3] "" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:75 #, javascript-format msgid "Yesterday・%s" msgstr "Учора・%s" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:243 #, javascript-format msgid "You: %s" msgstr "Вы: %s" #: src/service/ui/service.js:31 msgid "Select a Device" msgstr "Выбраць прыладу" #: src/service/ui/service.js:35 msgid "Select" msgstr "Выбраць" #: src/service/ui/settings.js:331 msgid "Unpaired" msgstr "Разлучана" #: src/service/ui/settings.js:333 msgid "Disconnected" msgstr "Адлучана" #: src/service/ui/settings.js:336 msgid "Connected" msgstr "Падлучана" #. TRANSLATORS: Description of where directly shared files are stored. #: src/service/ui/settings.js:406 #, javascript-format msgid "Transferred files are placed in the Downloads folder." msgstr "Перанесеныя файлы размяшчаюцца ў папцы Спампоўкі." #: src/service/ui/settings.js:499 msgid "A complete KDE Connect implementation for GNOME" msgstr "Паўнавартасная рэалізацыя KDE Connect для GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/service/ui/settings.js:508 msgid "translator-credits" msgstr "Maksim Krapiŭka " #: src/service/ui/settings.js:537 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "Паведамленні адладкі былі запісаны ў журнал. Зрабіце любыя крокі неабходныя для ўзнаўлення праблемы і затым праглядзіце журнал." #: src/service/ui/settings.js:540 msgid "Review Log" msgstr "Праверыць журнал" #. TRANSLATORS: When the battery level is 100% #: src/shell/device.js:82 msgid "Fully Charged" msgstr "Зараджана цалкам" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:86 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (Ацэнка…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:96 #, 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:104 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d застаецца)" #: src/shell/donotdisturb.js:150 src/shell/donotdisturb.js:289 msgid "Do Not Disturb" msgstr "Не турбаваць" #: src/shell/donotdisturb.js:156 msgid "Silence Mobile Device Notifications" msgstr "Адключыць апавяшчэнні прылады" #: src/shell/donotdisturb.js:171 msgid "Until you turn off Do Not Disturb" msgstr "Пакуль вы не адключыце «Не турбаваць»" #. TRANSLATORS: Time until change with time duration #. EXAMPLE: Until 10:00 (2 hours) #: src/shell/donotdisturb.js:184 src/shell/donotdisturb.js:278 #, javascript-format msgid "Until %s (%s)" msgstr "Да %s (%s)" #: src/shell/donotdisturb.js:216 msgid "Done" msgstr "Гатова" #. TRANSLATORS: Time duration in hours (eg. 2 hours) #: src/shell/donotdisturb.js:263 #, javascript-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "%d гадзіна" msgstr[1] "%d гадзіны" msgstr[2] "%d гадзін" msgstr[3] "" #: src/shell/notification.js:42 msgid "Reply" msgstr "Адказаць" #. TRANSLATORS: Extension name #: webextension/gettext.js:27 msgid "GSConnect" msgstr "GSConnect" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:29 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:33 msgid "Service Unavailable" msgstr "Сэрвіс недаступны" #. TRANSLATORS: No devices are known or available #: webextension/gettext.js:35 msgid "No Device Found" msgstr "Не знойдзена прылад" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:37 msgid "Open in Browser" msgstr "Адкрыць у браўзеры" gnome-shell-extension-gsconnect-20/po/ca-ES.po000066400000000000000000000505201341554142200213530ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-12-29 16:44-0800\n" "PO-Revision-Date: 2018-12-31 05:33\n" "Last-Translator: andyholmes \n" "Language-Team: Catalan\n" "Language: ca\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: crowdin.com\n" "X-Crowdin-Project: gsconnect\n" "X-Crowdin-Language: ca\n" "X-Crowdin-File: /master/po/org.gnome.Shell.Extensions.GSConnect.pot\n" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/connect.ui:24 data/menus.ui:7 msgid "Connect to…" msgstr "" #. Action Buttons #: data/connect.ui:30 data/notification.ui:14 data/telephony.ui:14 #: src/service/plugins/share.js:139 src/service/plugins/share.js:310 #: src/service/plugins/share.js:456 src/service/ui/device.js:607 #: src/service/ui/keybindings.js:44 src/service/ui/service.js:37 #: src/service/ui/settings.js:539 src/shell/donotdisturb.js:215 msgid "Cancel" msgstr "" #: data/connect.ui:37 msgid "Connect" msgstr "" #: data/connect.ui:98 msgid "IP Address" msgstr "" #: data/connect.ui:142 msgid "Bluetooth Device" msgstr "" #: data/contacts.ui:49 msgid "Type a phone number or name" msgstr "" #: data/contacts.ui:83 msgid "No contacts" msgstr "" #: data/contacts.ui:95 data/menus.ui:33 data/messaging.ui:247 msgid "Help" msgstr "" #: data/conversation.ui:76 data/conversation.ui:85 src/shell/notification.js:51 msgid "Type a message" msgstr "" #: data/conversation.ui:84 msgid "Send Message" msgstr "" #: data/device.ui:67 src/service/plugins/battery.js:12 msgid "Battery" msgstr "" #: data/device.ui:122 msgid "Clipboard Sync" msgstr "" #: data/device.ui:185 msgid "Media Players" msgstr "" #: data/device.ui:240 msgid "Mouse & Keyboard" msgstr "" #: data/device.ui:295 msgid "Volume Control" msgstr "" #: data/device.ui:345 data/device.ui:1728 msgid "Sharing" msgstr "" #: data/device.ui:374 data/device.ui:651 data/device.ui:1774 #: src/service/plugins/runcommand.js:18 src/service/plugins/runcommand.js:169 msgid "Commands" msgstr "" #: data/device.ui:424 data/device.ui:427 msgid "Name" msgstr "" #: data/device.ui:440 data/device.ui:446 msgid "Command Line" msgstr "" #: data/device.ui:444 data/device.ui:445 msgid "Choose an executable" msgstr "" #: data/device.ui:496 data/device.ui:511 msgid "Add" msgstr "" #: data/device.ui:527 data/device.ui:542 msgid "Remove" msgstr "" #: data/device.ui:576 data/device.ui:588 msgid "Edit" msgstr "" #: data/device.ui:604 data/device.ui:616 msgid "Save" msgstr "" #: data/device.ui:712 msgid "Share Notifications" msgstr "" #: data/device.ui:763 msgid "Applications" msgstr "" #: data/device.ui:809 data/device.ui:1820 #: src/service/plugins/notification.js:13 msgid "Notifications" msgstr "" #: data/device.ui:839 msgid "Incoming Calls" msgstr "" #: data/device.ui:888 data/device.ui:1055 msgid "Volume" msgstr "" #: data/device.ui:954 data/device.ui:1120 msgid "Pause Media" msgstr "" #: data/device.ui:1007 msgid "Ongoing Calls" msgstr "" #: data/device.ui:1176 msgid "Mute Microphone" msgstr "" #: data/device.ui:1230 data/device.ui:1866 src/service/plugins/telephony.js:13 msgid "Telephony" msgstr "" #: data/device.ui:1265 msgid "Action Shortcuts" msgstr "" #: data/device.ui:1280 data/device.ui:1349 msgid "Reset All…" msgstr "" #: data/device.ui:1334 msgid "Command Shortcuts" msgstr "" #: data/device.ui:1398 msgid "Shortcuts" msgstr "" #: data/device.ui:1429 msgid "Plugins" msgstr "" #: data/device.ui:1475 msgid "Experimental" msgstr "" #: data/device.ui:1524 msgid "Legacy SMS Support" msgstr "" #: data/device.ui:1598 msgid "Delete" msgstr "" #: data/device.ui:1627 msgid "Delete this device" msgstr "" #: data/device.ui:1645 msgid "Unpair and remove all settings and files" msgstr "" #: data/device.ui:1678 data/device.ui:1958 msgid "Advanced" msgstr "" #: data/device.ui:1912 msgid "Keyboard Shortcuts" msgstr "" #. TRANSLATORS: Send a pair request to the device #: data/device.ui:2015 data/menus.ui:68 src/service/device.js:424 msgid "Pair" msgstr "" #: data/device.ui:2047 msgid "Device is unpaired" msgstr "" #: data/device.ui:2062 msgid "You may configure this device before pairing" msgstr "" #: data/menus.ui:12 msgid "Display Mode" msgstr "" #. TRANSLATORS: Show device indicators in the top bar #: data/menus.ui:15 msgid "Panel" msgstr "" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/menus.ui:21 msgid "User Menu" msgstr "" #. TRANSLATORS: Generate a support log #: data/menus.ui:29 src/service/ui/settings.js:536 msgid "Generate Support Log" msgstr "" #: data/menus.ui:38 msgid "About" msgstr "" #. TRANSLATORS: Change the connection type to Bluetooth #: data/menus.ui:49 msgid "Switch to Bluetooth" msgstr "" #. TRANSLATORS: Change the connection type to TCP/IP #: data/menus.ui:56 msgid "Switch to LAN" msgstr "" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/menus.ui:63 src/service/ui/device.js:317 msgid "Encryption Info" msgstr "" #. TRANSLATORS: Unpair the device and notify it #: data/menus.ui:74 src/service/device.js:432 msgid "Unpair" msgstr "" #. TRANSLATORS: Send clipboard content to device #: data/menus.ui:86 msgid "To Device" msgstr "" #. TRANSLATORS: Receive clipboard content from the device #: data/menus.ui:92 msgid "From Device" msgstr "" #. TRANSLATORS: Don't change the system volume #: data/menus.ui:104 data/menus.ui:130 msgid "Nothing" msgstr "" #. TRANSLATORS: Lower the system volume #: data/menus.ui:111 data/menus.ui:137 msgid "Lower" msgstr "" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the phone ringer #: data/menus.ui:118 data/menus.ui:144 src/service/plugins/telephony.js:194 msgid "Mute" msgstr "" #: data/messaging.ui:12 src/service/plugins/sms.js:26 #: src/service/ui/messaging.js:911 msgid "Messaging" msgstr "" #: data/messaging.ui:21 src/service/ui/messaging.js:992 msgid "New Conversation" msgstr "" #: data/messaging.ui:110 msgid "No conversation selected" msgstr "" #: data/messaging.ui:126 msgid "Select or start a conversation" msgstr "" #: data/messaging.ui:193 data/notification.ui:52 data/telephony.ui:52 msgid "Device is disconnected" msgstr "" #: data/messaging.ui:264 msgid "No Conversations" msgstr "" #: data/notification.ui:21 data/telephony.ui:21 #: src/service/plugins/share.js:457 msgid "Send" msgstr "" #: data/settings.ui:10 msgid "Searching for devices…" msgstr "" #: data/settings.ui:49 data/settings.ui:63 msgid "Refresh" msgstr "" #: data/settings.ui:74 data/settings.ui:91 src/extension.js:105 msgid "Mobile Settings" msgstr "" #: data/settings.ui:144 data/settings.ui:158 msgid "Edit Device Name" msgstr "" #: data/settings.ui:236 msgid "Devices" msgstr "" #: data/settings.ui:299 msgid "Browser Add-Ons" msgstr "" #: data/settings.ui:579 msgid "Enable" msgstr "" #: data/settings.ui:611 msgid "This device is invisible to unpaired devices" msgstr "" #: data/settings.ui:623 src/service/daemon.js:532 msgid "Discovery Disabled" msgstr "" #. TRANSLATORS: Share URL by SMS #: data/telephony.ui:9 src/service/daemon.js:718 src/service/plugins/sms.js:50 #: webextension/gettext.js:39 msgid "Send SMS" msgstr "" #. Service Menu #: src/extension.js:79 src/extension.js:255 msgid "Mobile Devices" msgstr "" #. TRANSLATORS: %d is the number of devices connected #: src/extension.js:253 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "" msgstr[1] "" #. TRANSLATORS: Top-level context menu item for GSConnect #: src/nautilus-gsconnect.py:112 webextension/gettext.js:31 msgid "Send To Mobile Device" msgstr "" #: src/service/daemon.js:372 msgid "Report" msgstr "" #: src/service/daemon.js:507 msgid "Authentication Failure" msgstr "" #: src/service/daemon.js:516 msgid "Network Error" msgstr "" #: src/service/daemon.js:517 src/service/daemon.js:525 msgid "Click for help troubleshooting" msgstr "" #: src/service/daemon.js:524 msgid "PulseAudio Error" msgstr "" #: src/service/daemon.js:533 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "" #: src/service/daemon.js:541 #, javascript-format msgid "%s Plugin Failed To Load" msgstr "" #: src/service/daemon.js:542 msgid "Click for more information" msgstr "" #: src/service/daemon.js:724 msgid "Dial Number" msgstr "" #: src/service/daemon.js:730 src/service/plugins/share.js:27 msgid "Share File" msgstr "" #: src/service/device.js:161 msgid "Not available" msgstr "" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:165 #, javascript-format msgid "Bluetooth device at %s" msgstr "" #. TRANSLATORS: Label for TLS Certificate fingerprint #. #. Example: #. #. Google Pixel Fingerprint: #. 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 #: src/service/device.js:183 src/service/device.js:185 #, javascript-format msgid "%s Fingerprint:" msgstr "" #: src/service/device.js:242 msgid "Laptop" msgstr "" #: src/service/device.js:244 msgid "Smartphone" msgstr "" #: src/service/device.js:246 msgid "Tablet" msgstr "" #: src/service/device.js:248 msgid "Desktop" msgstr "" #: src/service/device.js:408 src/service/ui/device.js:15 msgid "Reconnect" msgstr "" #: src/service/device.js:416 src/service/ui/device.js:16 #: src/service/ui/settings.js:371 msgid "Settings" msgstr "" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:607 #, javascript-format msgid "Pair Request from %s" msgstr "" #: src/service/device.js:614 msgid "Reject" msgstr "" #: src/service/device.js:619 msgid "Accept" msgstr "" #: src/service/plugins/battery.js:188 src/service/plugins/findmyphone.js:19 msgid "Ring" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:197 #, javascript-format msgid "%s: Battery is low" msgstr "" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:199 #, javascript-format msgid "%d%% remaining" msgstr "" #: src/service/plugins/clipboard.js:11 msgid "Clipboard" msgstr "" #: src/service/plugins/clipboard.js:17 msgid "Clipboard Push" msgstr "" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Pull" msgstr "" #: src/service/plugins/contacts.js:12 msgid "Contacts" msgstr "" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #: src/service/plugins/contacts.js:263 src/service/plugins/telephony.js:173 #: src/service/plugins/telephony.js:224 src/service/plugins/telephony.js:264 #: src/service/ui/contacts.js:156 src/service/ui/contacts.js:397 msgid "Unknown Contact" msgstr "" #: src/service/plugins/findmyphone.js:13 msgid "Find My Phone" msgstr "" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "" #: src/service/plugins/mousepad.js:28 src/service/plugins/mousepad.js:569 msgid "Keyboard" msgstr "" #: src/service/plugins/mousepad.js:223 msgid "Additional Software Required" msgstr "" #: src/service/plugins/mousepad.js:586 msgid "Keyboard not ready" msgstr "" #: src/service/plugins/mpris.js:10 msgid "MPRIS" msgstr "" #: src/service/plugins/notification.js:26 msgid "Cancel Notification" msgstr "" #: src/service/plugins/notification.js:34 msgid "Close Notification" msgstr "" #: src/service/plugins/notification.js:42 msgid "Reply Notification" msgstr "" #: src/service/plugins/notification.js:50 msgid "Send Notification" msgstr "" #: src/service/plugins/ping.js:11 src/service/plugins/ping.js:17 #: src/service/plugins/ping.js:46 msgid "Ping" msgstr "" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:53 #, javascript-format msgid "Ping: %s" msgstr "" #: src/service/plugins/runcommand.js:12 msgid "Run Commands" msgstr "" #: src/service/plugins/sftp.js:12 msgid "SFTP" msgstr "" #: src/service/plugins/sftp.js:18 msgid "Mount" msgstr "" #: src/service/plugins/sftp.js:26 msgid "Unmount" msgstr "" #: src/service/plugins/sftp.js:159 msgid "All files" msgstr "" #: src/service/plugins/sftp.js:160 msgid "Camera pictures" msgstr "" #: src/service/plugins/sftp.js:417 msgid "Files" msgstr "" #: src/service/plugins/share.js:13 src/service/plugins/share.js:19 msgid "Share" msgstr "" #: src/service/plugins/share.js:35 msgid "Share Text" msgstr "" #: src/service/plugins/share.js:43 src/service/ui/messaging.js:975 #: src/service/ui/messaging.js:983 msgid "Share Link" msgstr "" #: src/service/plugins/share.js:132 src/service/plugins/share.js:303 msgid "Starting Transfer" msgstr "" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:134 #, javascript-format msgid "Receiving “%s” from %s" msgstr "" #: src/service/plugins/share.js:172 src/service/plugins/share.js:327 msgid "Transfer Successful" msgstr "" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:174 #, javascript-format msgid "Received “%s” from %s" msgstr "" #: src/service/plugins/share.js:180 msgid "Open Folder" msgstr "" #: src/service/plugins/share.js:185 msgid "Open File" msgstr "" #: src/service/plugins/share.js:192 src/service/plugins/share.js:335 msgid "Transfer Failed" msgstr "" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:194 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "" #: src/service/plugins/share.js:233 #, javascript-format msgid "Text Shared By %s" msgstr "" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:305 #, javascript-format msgid "Sending “%s” to %s" msgstr "" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:329 #, javascript-format msgid "Sent “%s” to %s" msgstr "" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:337 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:408 #, javascript-format msgid "Send files to %s" msgstr "" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:412 msgid "Open when done" msgstr "" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:451 #, javascript-format msgid "Send a link to %s" msgstr "" #: src/service/plugins/sms.js:13 msgid "SMS" msgstr "" #: src/service/plugins/sms.js:34 msgid "New SMS (URI)" msgstr "" #: src/service/plugins/sms.js:42 src/service/plugins/telephony.js:22 msgid "Reply SMS" msgstr "" #: src/service/plugins/sms.js:58 msgid "Share SMS" msgstr "" #: src/service/plugins/systemvolume.js:11 msgid "System Volume" msgstr "" #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:190 msgid "Incoming call" msgstr "" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:205 msgid "Ongoing call" msgstr "" #. TRANSLATORS: All other phone number types #: src/service/ui/contacts.js:118 src/service/ui/contacts.js:139 #, javascript-format msgid "%s・Other" msgstr "" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:123 #, javascript-format msgid "%s・Fax" msgstr "" #. TRANSLATORS: A work phone number #: src/service/ui/contacts.js:127 #, javascript-format msgid "%s・Work" msgstr "" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:131 #, javascript-format msgid "%s・Mobile" msgstr "" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:135 #, javascript-format msgid "%s・Home" msgstr "" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #: src/service/ui/contacts.js:278 src/service/ui/contacts.js:292 #, javascript-format msgid "Send to %s" msgstr "" #: src/service/ui/device.js:608 msgid "Open" msgstr "" #: src/service/ui/device.js:660 src/service/ui/device.js:673 msgid "On" msgstr "" #: src/service/ui/device.js:660 src/service/ui/device.js:673 msgid "Off" msgstr "" #: src/service/ui/device.js:798 src/service/ui/device.js:826 #: src/service/ui/device.js:850 msgid "Disabled" msgstr "" #. TRANSLATORS: Title of keyboard shortcut dialog #: src/service/ui/keybindings.js:33 msgid "Set Shortcut" msgstr "" #. TRANSLATORS: Button to confirm the new shortcut #: src/service/ui/keybindings.js:47 msgid "Set" msgstr "" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/service/ui/keybindings.js:54 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "" #. TRANSLATORS: Keys for cancelling (␛) or resetting (␈) a shortcut #: src/service/ui/keybindings.js:83 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/service/ui/keybindings.js:182 #, javascript-format msgid "%s is already being used" msgstr "" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:65 src/service/ui/messaging.js:98 msgid "Just now" msgstr "" #. TRANSLATORS: Time duration in minutes (eg. 15 minutes) #: src/service/ui/messaging.js:70 src/service/ui/messaging.js:102 #: src/shell/donotdisturb.js:266 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "" msgstr[1] "" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:75 #, javascript-format msgid "Yesterday・%s" msgstr "" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:243 #, javascript-format msgid "You: %s" msgstr "" #: src/service/ui/service.js:31 msgid "Select a Device" msgstr "" #: src/service/ui/service.js:35 msgid "Select" msgstr "" #: src/service/ui/settings.js:331 msgid "Unpaired" msgstr "" #: src/service/ui/settings.js:333 msgid "Disconnected" msgstr "" #: src/service/ui/settings.js:336 msgid "Connected" msgstr "" #. TRANSLATORS: Description of where directly shared files are stored. #: src/service/ui/settings.js:406 #, javascript-format msgid "Transferred files are placed in the Downloads folder." msgstr "" #: src/service/ui/settings.js:499 msgid "A complete KDE Connect implementation for GNOME" msgstr "" #. TRANSLATORS: eg. 'Translator Name ' #: src/service/ui/settings.js:508 msgid "translator-credits" msgstr "" #: src/service/ui/settings.js:537 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "" #: src/service/ui/settings.js:540 msgid "Review Log" msgstr "" #. TRANSLATORS: When the battery level is 100% #: src/shell/device.js:82 msgid "Fully Charged" msgstr "" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:86 #, javascript-format msgid "%d%% (Estimating…)" msgstr "" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:96 #, 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:104 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "" #: src/shell/donotdisturb.js:150 src/shell/donotdisturb.js:289 msgid "Do Not Disturb" msgstr "" #: src/shell/donotdisturb.js:156 msgid "Silence Mobile Device Notifications" msgstr "" #: src/shell/donotdisturb.js:171 msgid "Until you turn off Do Not Disturb" msgstr "" #. TRANSLATORS: Time until change with time duration #. EXAMPLE: Until 10:00 (2 hours) #: src/shell/donotdisturb.js:184 src/shell/donotdisturb.js:278 #, javascript-format msgid "Until %s (%s)" msgstr "" #: src/shell/donotdisturb.js:216 msgid "Done" msgstr "" #. TRANSLATORS: Time duration in hours (eg. 2 hours) #: src/shell/donotdisturb.js:263 #, javascript-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "" msgstr[1] "" #: src/shell/notification.js:42 msgid "Reply" msgstr "" #. TRANSLATORS: Extension name #: webextension/gettext.js:27 msgid "GSConnect" msgstr "" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:29 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "" #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:33 msgid "Service Unavailable" msgstr "" #. TRANSLATORS: No devices are known or available #: webextension/gettext.js:35 msgid "No Device Found" msgstr "" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:37 msgid "Open in Browser" msgstr "" gnome-shell-extension-gsconnect-20/po/ca.po000066400000000000000000000544431341554142200210560ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-12-29 16:44-0800\n" "PO-Revision-Date: 2019-01-08 13:45\n" "Last-Translator: andyholmes \n" "Language-Team: Catalan\n" "Language: ca\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: crowdin.com\n" "X-Crowdin-Project: gsconnect\n" "X-Crowdin-Language: ca\n" "X-Crowdin-File: /master/po/org.gnome.Shell.Extensions.GSConnect.pot\n" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/connect.ui:24 data/menus.ui:7 msgid "Connect to…" msgstr "Connecta amb…" #. Action Buttons #: data/connect.ui:30 data/notification.ui:14 data/telephony.ui:14 #: src/service/plugins/share.js:139 src/service/plugins/share.js:310 #: src/service/plugins/share.js:456 src/service/ui/device.js:607 #: src/service/ui/keybindings.js:44 src/service/ui/service.js:37 #: src/service/ui/settings.js:539 src/shell/donotdisturb.js:215 msgid "Cancel" msgstr "Cancel·la" #: data/connect.ui:37 msgid "Connect" msgstr "Connecta" #: data/connect.ui:98 msgid "IP Address" msgstr "Adreça IP" #: data/connect.ui:142 msgid "Bluetooth Device" msgstr "Dispositiu Bluetooth" #: data/contacts.ui:49 msgid "Type a phone number or name" msgstr "" #: data/contacts.ui:83 msgid "No contacts" msgstr "No hi ha cap contacte" #: data/contacts.ui:95 data/menus.ui:33 data/messaging.ui:247 msgid "Help" msgstr "Ajuda" #: data/conversation.ui:76 data/conversation.ui:85 src/shell/notification.js:51 msgid "Type a message" msgstr "Escriviu un missatge" #: data/conversation.ui:84 msgid "Send Message" msgstr "Envia el missatge" #: data/device.ui:67 src/service/plugins/battery.js:12 msgid "Battery" msgstr "Bateria" #: data/device.ui:122 msgid "Clipboard Sync" msgstr "Sincronització del porta-retalls" #: data/device.ui:185 msgid "Media Players" msgstr "Reproductors multimèdia" #: data/device.ui:240 msgid "Mouse & Keyboard" msgstr "Ratolí i teclat" #: data/device.ui:295 msgid "Volume Control" msgstr "Control de volum" #: data/device.ui:345 data/device.ui:1728 msgid "Sharing" msgstr "Compartició" #: data/device.ui:374 data/device.ui:651 data/device.ui:1774 #: src/service/plugins/runcommand.js:18 src/service/plugins/runcommand.js:169 msgid "Commands" msgstr "Ordres" #: data/device.ui:424 data/device.ui:427 msgid "Name" msgstr "Nom" #: data/device.ui:440 data/device.ui:446 msgid "Command Line" msgstr "Línia d’ordres" #: data/device.ui:444 data/device.ui:445 msgid "Choose an executable" msgstr "Trieu un executable" #: data/device.ui:496 data/device.ui:511 msgid "Add" msgstr "Afegeix" #: data/device.ui:527 data/device.ui:542 msgid "Remove" msgstr "Elimina" #: data/device.ui:576 data/device.ui:588 msgid "Edit" msgstr "Edita" #: data/device.ui:604 data/device.ui:616 msgid "Save" msgstr "Desa" #: data/device.ui:712 msgid "Share Notifications" msgstr "Notificacions de compartició" #: data/device.ui:763 msgid "Applications" msgstr "Aplicacions" #: data/device.ui:809 data/device.ui:1820 #: src/service/plugins/notification.js:13 msgid "Notifications" msgstr "Notificacions" #: data/device.ui:839 msgid "Incoming Calls" msgstr "Trucades entrants" #: data/device.ui:888 data/device.ui:1055 msgid "Volume" msgstr "Volum" #: data/device.ui:954 data/device.ui:1120 msgid "Pause Media" msgstr "" #: data/device.ui:1007 msgid "Ongoing Calls" msgstr "Trucades en curs" #: data/device.ui:1176 msgid "Mute Microphone" msgstr "" #: data/device.ui:1230 data/device.ui:1866 src/service/plugins/telephony.js:13 msgid "Telephony" msgstr "Telefonia" #: data/device.ui:1265 msgid "Action Shortcuts" msgstr "Dreceres d’accions" #: data/device.ui:1280 data/device.ui:1349 msgid "Reset All…" msgstr "Reinicialitza-ho tot…" #: data/device.ui:1334 msgid "Command Shortcuts" msgstr "Dreceres d’ordres" #: data/device.ui:1398 msgid "Shortcuts" msgstr "Dreceres" #: data/device.ui:1429 msgid "Plugins" msgstr "Connectors" #: data/device.ui:1475 msgid "Experimental" msgstr "Experiments" #: data/device.ui:1524 msgid "Legacy SMS Support" msgstr "" #: data/device.ui:1598 msgid "Delete" msgstr "Suprimeix" #: data/device.ui:1627 msgid "Delete this device" msgstr "Suprimeix aquest dispositiu" #: data/device.ui:1645 msgid "Unpair and remove all settings and files" msgstr "" #: data/device.ui:1678 data/device.ui:1958 msgid "Advanced" msgstr "Avançat" #: data/device.ui:1912 msgid "Keyboard Shortcuts" msgstr "Dreceres de teclat" #. TRANSLATORS: Send a pair request to the device #: data/device.ui:2015 data/menus.ui:68 src/service/device.js:424 msgid "Pair" msgstr "" #: data/device.ui:2047 msgid "Device is unpaired" msgstr "" #: data/device.ui:2062 msgid "You may configure this device before pairing" msgstr "" #: data/menus.ui:12 msgid "Display Mode" msgstr "" #. TRANSLATORS: Show device indicators in the top bar #: data/menus.ui:15 msgid "Panel" msgstr "Plafó" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/menus.ui:21 msgid "User Menu" msgstr "Menú d’usuari" #. TRANSLATORS: Generate a support log #: data/menus.ui:29 src/service/ui/settings.js:536 msgid "Generate Support Log" msgstr "" #: data/menus.ui:38 msgid "About" msgstr "Quant a" #. TRANSLATORS: Change the connection type to Bluetooth #: data/menus.ui:49 msgid "Switch to Bluetooth" msgstr "Canvia a Bluetooth" #. TRANSLATORS: Change the connection type to TCP/IP #: data/menus.ui:56 msgid "Switch to LAN" msgstr "Canvia a LAN" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/menus.ui:63 src/service/ui/device.js:317 msgid "Encryption Info" msgstr "Informació de xifratge" #. TRANSLATORS: Unpair the device and notify it #: data/menus.ui:74 src/service/device.js:432 msgid "Unpair" msgstr "" #. TRANSLATORS: Send clipboard content to device #: data/menus.ui:86 msgid "To Device" msgstr "Al dispositiu" #. TRANSLATORS: Receive clipboard content from the device #: data/menus.ui:92 msgid "From Device" msgstr "Des del dispositiu" #. TRANSLATORS: Don't change the system volume #: data/menus.ui:104 data/menus.ui:130 msgid "Nothing" msgstr "" #. TRANSLATORS: Lower the system volume #: data/menus.ui:111 data/menus.ui:137 msgid "Lower" msgstr "" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the phone ringer #: data/menus.ui:118 data/menus.ui:144 src/service/plugins/telephony.js:194 msgid "Mute" msgstr "Silencia" #: data/messaging.ui:12 src/service/plugins/sms.js:26 #: src/service/ui/messaging.js:911 msgid "Messaging" msgstr "Missatgeria" #: data/messaging.ui:21 src/service/ui/messaging.js:992 msgid "New Conversation" msgstr "Conversa nova" #: data/messaging.ui:110 msgid "No conversation selected" msgstr "No s’ha seleccionat cap conversa" #: data/messaging.ui:126 msgid "Select or start a conversation" msgstr "Seleccioneu una conversa o inicieu-ne una de nova" #: data/messaging.ui:193 data/notification.ui:52 data/telephony.ui:52 msgid "Device is disconnected" msgstr "" #: data/messaging.ui:264 msgid "No Conversations" msgstr "No hi ha cap conversa" #: data/notification.ui:21 data/telephony.ui:21 #: src/service/plugins/share.js:457 msgid "Send" msgstr "Envia" #: data/settings.ui:10 msgid "Searching for devices…" msgstr "S’estan cercant dispositius…" #: data/settings.ui:49 data/settings.ui:63 msgid "Refresh" msgstr "Actualitza" #: data/settings.ui:74 data/settings.ui:91 src/extension.js:105 msgid "Mobile Settings" msgstr "" #: data/settings.ui:144 data/settings.ui:158 msgid "Edit Device Name" msgstr "Edita el nom del dispositiu" #: data/settings.ui:236 msgid "Devices" msgstr "Dispositius" #: data/settings.ui:299 msgid "Browser Add-Ons" msgstr "Connectors per als navegadors" #: data/settings.ui:579 msgid "Enable" msgstr "" #: data/settings.ui:611 msgid "This device is invisible to unpaired devices" msgstr "" #: data/settings.ui:623 src/service/daemon.js:532 msgid "Discovery Disabled" msgstr "" #. TRANSLATORS: Share URL by SMS #: data/telephony.ui:9 src/service/daemon.js:718 src/service/plugins/sms.js:50 #: webextension/gettext.js:39 msgid "Send SMS" msgstr "" #. Service Menu #: src/extension.js:79 src/extension.js:255 msgid "Mobile Devices" msgstr "Dispositius mòbils" #. TRANSLATORS: %d is the number of devices connected #: src/extension.js:253 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "" msgstr[1] "" #. TRANSLATORS: Top-level context menu item for GSConnect #: src/nautilus-gsconnect.py:112 webextension/gettext.js:31 msgid "Send To Mobile Device" msgstr "" #: src/service/daemon.js:372 msgid "Report" msgstr "" #: src/service/daemon.js:507 msgid "Authentication Failure" msgstr "" #: src/service/daemon.js:516 msgid "Network Error" msgstr "" #: src/service/daemon.js:517 src/service/daemon.js:525 msgid "Click for help troubleshooting" msgstr "" #: src/service/daemon.js:524 msgid "PulseAudio Error" msgstr "" #: src/service/daemon.js:533 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "" #: src/service/daemon.js:541 #, javascript-format msgid "%s Plugin Failed To Load" msgstr "" #: src/service/daemon.js:542 msgid "Click for more information" msgstr "Feu clic per a més informació" #: src/service/daemon.js:724 msgid "Dial Number" msgstr "" #: src/service/daemon.js:730 src/service/plugins/share.js:27 msgid "Share File" msgstr "" #: src/service/device.js:161 msgid "Not available" msgstr "No disponible" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:165 #, javascript-format msgid "Bluetooth device at %s" msgstr "" #. TRANSLATORS: Label for TLS Certificate fingerprint #. #. Example: #. #. Google Pixel Fingerprint: #. 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 #: src/service/device.js:183 src/service/device.js:185 #, javascript-format msgid "%s Fingerprint:" msgstr "Empremta de l’aparell %s:" #: src/service/device.js:242 msgid "Laptop" msgstr "Ordinador portàtil" #: src/service/device.js:244 msgid "Smartphone" msgstr "Telèfon intel·ligent" #: src/service/device.js:246 msgid "Tablet" msgstr "Tauleta" #: src/service/device.js:248 msgid "Desktop" msgstr "Ordinador d’escriptori" #: src/service/device.js:408 src/service/ui/device.js:15 msgid "Reconnect" msgstr "" #: src/service/device.js:416 src/service/ui/device.js:16 #: src/service/ui/settings.js:371 msgid "Settings" msgstr "" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:607 #, javascript-format msgid "Pair Request from %s" msgstr "" #: src/service/device.js:614 msgid "Reject" msgstr "Rebutja’l" #: src/service/device.js:619 msgid "Accept" msgstr "Accepta’l" #: src/service/plugins/battery.js:188 src/service/plugins/findmyphone.js:19 msgid "Ring" msgstr "Truca" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:197 #, javascript-format msgid "%s: Battery is low" msgstr "" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:199 #, javascript-format msgid "%d%% remaining" msgstr "" #: src/service/plugins/clipboard.js:11 msgid "Clipboard" msgstr "Porta-retalls" #: src/service/plugins/clipboard.js:17 msgid "Clipboard Push" msgstr "" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Pull" msgstr "" #: src/service/plugins/contacts.js:12 msgid "Contacts" msgstr "Contactes" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #: src/service/plugins/contacts.js:263 src/service/plugins/telephony.js:173 #: src/service/plugins/telephony.js:224 src/service/plugins/telephony.js:264 #: src/service/ui/contacts.js:156 src/service/ui/contacts.js:397 msgid "Unknown Contact" msgstr "Contacte desconegut" #: src/service/plugins/findmyphone.js:13 msgid "Find My Phone" msgstr "Troba el meu telèfon" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "" #: src/service/plugins/mousepad.js:28 src/service/plugins/mousepad.js:569 msgid "Keyboard" msgstr "Teclat" #: src/service/plugins/mousepad.js:223 msgid "Additional Software Required" msgstr "" #: src/service/plugins/mousepad.js:586 msgid "Keyboard not ready" msgstr "El teclat no està preparat" #: src/service/plugins/mpris.js:10 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/notification.js:26 msgid "Cancel Notification" msgstr "" #: src/service/plugins/notification.js:34 msgid "Close Notification" msgstr "" #: src/service/plugins/notification.js:42 msgid "Reply Notification" msgstr "" #: src/service/plugins/notification.js:50 msgid "Send Notification" msgstr "Envia una notificació" #: src/service/plugins/ping.js:11 src/service/plugins/ping.js:17 #: src/service/plugins/ping.js:46 msgid "Ping" msgstr "Comprovació de connectivitat" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:53 #, javascript-format msgid "Ping: %s" msgstr "Comprovació de connectivitat: %s" #: src/service/plugins/runcommand.js:12 msgid "Run Commands" msgstr "Execució d’ordres" #: src/service/plugins/sftp.js:12 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:18 msgid "Mount" msgstr "Munta" #: src/service/plugins/sftp.js:26 msgid "Unmount" msgstr "Desmunta" #: src/service/plugins/sftp.js:159 msgid "All files" msgstr "Tots els fitxers" #: src/service/plugins/sftp.js:160 msgid "Camera pictures" msgstr "Fotografies de la càmera" #: src/service/plugins/sftp.js:417 msgid "Files" msgstr "Fitxers" #: src/service/plugins/share.js:13 src/service/plugins/share.js:19 msgid "Share" msgstr "Compartició" #: src/service/plugins/share.js:35 msgid "Share Text" msgstr "" #: src/service/plugins/share.js:43 src/service/ui/messaging.js:975 #: src/service/ui/messaging.js:983 msgid "Share Link" msgstr "" #: src/service/plugins/share.js:132 src/service/plugins/share.js:303 msgid "Starting Transfer" msgstr "S'està iniciant la transferència" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:134 #, javascript-format msgid "Receiving “%s” from %s" msgstr "S’està rebent «%s» del %s" #: src/service/plugins/share.js:172 src/service/plugins/share.js:327 msgid "Transfer Successful" msgstr "" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:174 #, javascript-format msgid "Received “%s” from %s" msgstr "S’ha rebut «%s» del %s" #: src/service/plugins/share.js:180 msgid "Open Folder" msgstr "Obre la carpeta" #: src/service/plugins/share.js:185 msgid "Open File" msgstr "Obre el fitxer" #: src/service/plugins/share.js:192 src/service/plugins/share.js:335 msgid "Transfer Failed" msgstr "" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:194 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "" #: src/service/plugins/share.js:233 #, javascript-format msgid "Text Shared By %s" msgstr "" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:305 #, javascript-format msgid "Sending “%s” to %s" msgstr "" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:329 #, javascript-format msgid "Sent “%s” to %s" msgstr "" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:337 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:408 #, 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:412 msgid "Open when done" msgstr "Obre’l en finalitzar" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:451 #, javascript-format msgid "Send a link to %s" msgstr "" #: src/service/plugins/sms.js:13 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:34 msgid "New SMS (URI)" msgstr "" #: src/service/plugins/sms.js:42 src/service/plugins/telephony.js:22 msgid "Reply SMS" msgstr "" #: src/service/plugins/sms.js:58 msgid "Share SMS" msgstr "" #: src/service/plugins/systemvolume.js:11 msgid "System Volume" msgstr "Volum del sistema" #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Silencia la trucada" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:190 msgid "Incoming call" msgstr "Trucada entrant" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:205 msgid "Ongoing call" msgstr "Trucada en curs" #. TRANSLATORS: All other phone number types #: src/service/ui/contacts.js:118 src/service/ui/contacts.js:139 #, javascript-format msgid "%s・Other" msgstr "%s・Altre" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:123 #, javascript-format msgid "%s・Fax" msgstr "%s・Fax" #. TRANSLATORS: A work phone number #: src/service/ui/contacts.js:127 #, javascript-format msgid "%s・Work" msgstr "%s・Feina" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:131 #, javascript-format msgid "%s・Mobile" msgstr "%s・Mòbil" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:135 #, javascript-format msgid "%s・Home" msgstr "%s・Particular" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #: src/service/ui/contacts.js:278 src/service/ui/contacts.js:292 #, javascript-format msgid "Send to %s" msgstr "Envia al %s" #: src/service/ui/device.js:608 msgid "Open" msgstr "Obre" #: src/service/ui/device.js:660 src/service/ui/device.js:673 msgid "On" msgstr "" #: src/service/ui/device.js:660 src/service/ui/device.js:673 msgid "Off" msgstr "" #: src/service/ui/device.js:798 src/service/ui/device.js:826 #: src/service/ui/device.js:850 msgid "Disabled" msgstr "" #. TRANSLATORS: Title of keyboard shortcut dialog #: src/service/ui/keybindings.js:33 msgid "Set Shortcut" msgstr "" #. TRANSLATORS: Button to confirm the new shortcut #: src/service/ui/keybindings.js:47 msgid "Set" msgstr "" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/service/ui/keybindings.js:54 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "" #. TRANSLATORS: Keys for cancelling (␛) or resetting (␈) a shortcut #: src/service/ui/keybindings.js:83 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/service/ui/keybindings.js:182 #, javascript-format msgid "%s is already being used" msgstr "%s ja s’està fent servir" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:65 src/service/ui/messaging.js:98 msgid "Just now" msgstr "Ara mateix" #. TRANSLATORS: Time duration in minutes (eg. 15 minutes) #: src/service/ui/messaging.js:70 src/service/ui/messaging.js:102 #: src/shell/donotdisturb.js:266 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minut" msgstr[1] "%d minuts" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:75 #, javascript-format msgid "Yesterday・%s" msgstr "Ahir・%s" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:243 #, javascript-format msgid "You: %s" msgstr "Vós: %s" #: src/service/ui/service.js:31 msgid "Select a Device" msgstr "Seleccioneu un dispositiu" #: src/service/ui/service.js:35 msgid "Select" msgstr "Selecciona" #: src/service/ui/settings.js:331 msgid "Unpaired" msgstr "" #: src/service/ui/settings.js:333 msgid "Disconnected" msgstr "Desconnectat" #: src/service/ui/settings.js:336 msgid "Connected" msgstr "Connectat" #. TRANSLATORS: Description of where directly shared files are stored. #: src/service/ui/settings.js:406 #, javascript-format msgid "Transferred files are placed in the Downloads folder." msgstr "" #: src/service/ui/settings.js:499 msgid "A complete KDE Connect implementation for GNOME" msgstr "" #. TRANSLATORS: eg. 'Translator Name ' #: src/service/ui/settings.js:508 msgid "translator-credits" msgstr "Adolfo Jayme Barrientos , 2019\n" "Marc Riera Irigoyen , 2019" #: src/service/ui/settings.js:537 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "" #: src/service/ui/settings.js:540 msgid "Review Log" msgstr "" #. TRANSLATORS: When the battery level is 100% #: src/shell/device.js:82 msgid "Fully Charged" msgstr "" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:86 #, javascript-format msgid "%d%% (Estimating…)" msgstr "" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:96 #, 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:104 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "" #: src/shell/donotdisturb.js:150 src/shell/donotdisturb.js:289 msgid "Do Not Disturb" msgstr "" #: src/shell/donotdisturb.js:156 msgid "Silence Mobile Device Notifications" msgstr "" #: src/shell/donotdisturb.js:171 msgid "Until you turn off Do Not Disturb" msgstr "" #. TRANSLATORS: Time until change with time duration #. EXAMPLE: Until 10:00 (2 hours) #: src/shell/donotdisturb.js:184 src/shell/donotdisturb.js:278 #, javascript-format msgid "Until %s (%s)" msgstr "" #: src/shell/donotdisturb.js:216 msgid "Done" msgstr "Fet" #. TRANSLATORS: Time duration in hours (eg. 2 hours) #: src/shell/donotdisturb.js:263 #, javascript-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "%d hora" msgstr[1] "%d hores" #: src/shell/notification.js:42 msgid "Reply" msgstr "Respon" #. TRANSLATORS: Extension name #: webextension/gettext.js:27 msgid "GSConnect" msgstr "GSConnect" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:29 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "" #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:33 msgid "Service Unavailable" msgstr "" #. TRANSLATORS: No devices are known or available #: webextension/gettext.js:35 msgid "No Device Found" msgstr "No s'ha trobat cap dispositiu" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:37 msgid "Open in Browser" msgstr "Obre al navegador" gnome-shell-extension-gsconnect-20/po/cs.po000066400000000000000000000535041341554142200210750ustar00rootroot00000000000000# 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. # Petr Šimáček , 2018. # msgid "" msgstr "" "Project-Id-Version: org.gnome.Shell.Extensions.GSConnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-11-19 11:14-0800\n" "PO-Revision-Date: 2018-05-02 09:22+1\n" "Last-Translator: Petr Šimáček \n" "Language-Team: Czech \n" "Language: cs\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" #: data/conversation.ui:71 data/conversation.ui:79 #, fuzzy msgid "Type a message" msgstr "Napsat zprávu SMS" #: data/contacts.ui:51 data/contacts.ui:52 msgid "Type a phone number or name" msgstr "Napište telefoní číslo nebo jméno" #: data/device.ui:68 src/service/plugins/battery.js:12 msgid "Battery" msgstr "Baterie" #: data/device.ui:116 #, fuzzy msgid "Clipboard Sync" msgstr "Schránka" #: data/device.ui:172 #, fuzzy msgid "Media Players" msgstr "Ovládání multimediálního přehrávače" #: data/device.ui:220 msgid "Mouse & Keyboard" msgstr "" #: data/device.ui:268 msgid "Volume Control" msgstr "" #: data/device.ui:308 data/device.ui:1406 #, fuzzy msgid "Sharing" msgstr "Sdílet odkaz" #: data/device.ui:337 data/device.ui:534 data/device.ui:1449 #: src/service/plugins/runcommand.js:18 src/service/plugins/runcommand.js:169 #, fuzzy msgid "Commands" msgstr "Příkaz" #: data/device.ui:385 msgid "Name" msgstr "Jméno" #: data/device.ui:401 data/device.ui:402 msgid "Choose an executable" msgstr "" #: data/device.ui:403 #, fuzzy msgid "Command Line" msgstr "Příkaz" #: data/device.ui:597 #, fuzzy msgid "Share Notifications" msgstr "Poslat upozornění" #: data/device.ui:636 msgid "Applications" msgstr "Aplikace" #: data/device.ui:675 data/device.ui:1492 #: src/service/plugins/notification.js:12 msgid "Notifications" msgstr "Upozornění" #: data/device.ui:705 msgid "Incoming Calls" msgstr "Příchozí hovory" #: data/device.ui:755 data/device.ui:900 #, fuzzy msgid "Volume" msgstr "Hlasitost systému" #: data/device.ui:813 data/device.ui:958 msgid "Pause Media" msgstr "Pozastavit multimédia" #: data/device.ui:852 #, fuzzy msgid "Ongoing Calls" msgstr "Příchozí hovory" #: data/device.ui:1007 msgid "Mute Microphone" msgstr "Ztišit mikrofon" #: data/device.ui:1047 data/device.ui:1535 src/service/plugins/telephony.js:12 msgid "Telephony" msgstr "Telefonie" #: data/device.ui:1082 #, fuzzy msgid "Action Shortcuts" msgstr "Klávesové zkratky" #: data/device.ui:1094 data/device.ui:1157 msgid "Reset All…" msgstr "" #: data/device.ui:1145 #, fuzzy msgid "Command Shortcuts" msgstr "Klávesové zkratky" #: data/device.ui:1203 #, fuzzy msgid "Shortcuts" msgstr "Klávesové zkratky" #: data/device.ui:1234 msgid "Plugins" msgstr "Zásuvné moduly" #: data/device.ui:1295 msgid "Delete" msgstr "" #: data/device.ui:1320 #, fuzzy msgid "Delete this device" msgstr "%s chce najít toto zařízení" #: data/device.ui:1335 msgid "Unpair and remove all settings and files" msgstr "" #: data/device.ui:1365 data/device.ui:1621 msgid "Advanced" msgstr "" #: data/device.ui:1578 #, fuzzy msgid "Keyboard Shortcuts" msgstr "Klávesové zkratky" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/menus.ui:7 src/service/ui/service.js:115 #, fuzzy msgid "Connect to…" msgstr "GSConnect" #: data/menus.ui:13 data/settings.ui:881 msgid "Help" msgstr "" #. TRANSLATORS: Open the developer's dialog #: data/menus.ui:19 #, fuzzy msgid "Debugger" msgstr "Mód ladění" #: data/menus.ui:23 msgid "About" msgstr "O aplikaci" #. TRANSLATORS: Change the connection type to Bluetooth #: data/menus.ui:34 msgid "Switch to Bluetooth" msgstr "" #. TRANSLATORS: Change the connection type to TCP/IP #: data/menus.ui:41 msgid "Switch to LAN" msgstr "" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/menus.ui:48 src/service/ui/settings.js:612 msgid "Encryption Info" msgstr "" #. TRANSLATORS: Send a pair request to the device #: data/menus.ui:53 src/service/device.js:425 msgid "Pair" msgstr "" #. TRANSLATORS: Unpair the device and notify it #: data/menus.ui:59 src/service/device.js:433 #, fuzzy msgid "Unpair" msgstr "Zobrazit nespárované" #. TRANSLATORS: Send clipboard content to device #: data/menus.ui:71 #, fuzzy msgid "To Device" msgstr "Zařízení" #. TRANSLATORS: Receive clipboard content from the device #: data/menus.ui:77 #, fuzzy msgid "From Device" msgstr "Najít zařízení" #. TRANSLATORS: Don't change the system volume #: data/menus.ui:89 data/menus.ui:115 msgid "Nothing" msgstr "Nic" #. TRANSLATORS: Lower the system volume #: data/menus.ui:96 data/menus.ui:122 msgid "Lower" msgstr "Dolní" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the phone ringer #: data/menus.ui:103 data/menus.ui:129 src/service/plugins/telephony.js:176 msgid "Mute" msgstr "Ztlumit" #: data/messaging.ui:12 src/service/plugins/sms.js:25 #: src/service/ui/messaging.js:871 #, fuzzy msgid "Messaging" msgstr "Vzkaz" #: data/messaging.ui:110 #, fuzzy msgid "Select a conversation" msgstr "Přidat lidi do konverzace" #: data/messaging.ui:188 msgid "Device is disconnected" msgstr "Zařízení je odpojeno" #: data/settings.ui:333 msgid "Appearance" msgstr "Vzhled" #: data/settings.ui:383 msgid "Display Mode" msgstr "" #: data/settings.ui:425 #, fuzzy msgid "Service" msgstr "Zařízení" #: data/settings.ui:475 msgid "Discoverable" msgstr "" #: data/settings.ui:528 #, fuzzy msgid "Restart Service" msgstr "Zařízení" #: data/settings.ui:581 data/settings.ui:1015 src/service/device.js:417 #, fuzzy msgid "Settings" msgstr "Nastavení telefonu" #: data/settings.ui:639 msgid "Remote Filesystems" msgstr "" #: data/settings.ui:675 msgid "Sound Effects" msgstr "" #: data/settings.ui:712 #, fuzzy msgid "Extended Keyboard Support" msgstr "Klávesové zkratky" #: data/settings.ui:749 #, fuzzy msgid "Desktop Contacts" msgstr "Neznámý kontakt" #: data/settings.ui:786 #, fuzzy msgid "Files Integration" msgstr "Propojení s Nautilem" #: data/settings.ui:813 msgid "Additional Features" msgstr "" #: data/settings.ui:903 msgid "Browser Add-Ons" msgstr "" #: data/settings.ui:921 #, fuzzy msgid "KDE Connect" msgstr "GSConnect" #: data/settings.ui:956 data/settings.ui:1058 msgid "Other" msgstr "Ostatní" #. TRANSLATORS: No devices are known or available #: data/settings.ui:1106 webextension/gettext.js:35 #, fuzzy msgid "No Device Found" msgstr "Zařízení je spárováno" #: data/settings.ui:1142 msgid "Refresh" msgstr "Obnovit" #. Service Menu #: src/extension.js:79 src/extension.js:259 msgid "Mobile Devices" msgstr "Mobilní zařízení" #: src/extension.js:105 msgid "Mobile Settings" msgstr "Nastavení telefonu" #. TRANSLATORS: Extension name #: src/extension.js:196 src/extension.js:311 src/service/daemon.js:95 #: src/service/daemon.js:413 webextension/gettext.js:27 msgid "GSConnect" msgstr "GSConnect" #. TRANSLATORS: %d is the number of devices connected #: src/extension.js:257 #, fuzzy, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "GSConnect" msgstr[1] "GSConnect" #. TRANSLATORS: Top-level context menu item for GSConnect #: src/nautilus-gsconnect.py:112 webextension/gettext.js:31 msgid "Send To Mobile Device" msgstr "Poslat do mobilního zařízení" #: src/service/daemon.js:406 #, fuzzy msgid "A complete KDE Connect implementation for GNOME" msgstr "KDE Connect implementovaný do GNOME Shell" #. TRANSLATORS: eg. 'Translator Name ' #: src/service/daemon.js:415 msgid "translator-credits" msgstr "" #: src/service/daemon.js:437 msgid "Report" msgstr "" #: src/service/daemon.js:567 msgid "Authentication Failure" msgstr "" #: src/service/daemon.js:576 msgid "Network Error" msgstr "" #: src/service/daemon.js:577 src/service/daemon.js:587 msgid "Click for help troubleshooting" msgstr "" #: src/service/daemon.js:586 msgid "PulseAudio Error" msgstr "" #: src/service/daemon.js:596 msgid "Discovery Disabled" msgstr "" #: src/service/daemon.js:597 msgid "" "Discovery has been disabled due to the number of devices on this network." msgstr "" #: src/service/daemon.js:599 src/service/daemon.js:609 msgid "Click to open preferences" msgstr "" #: src/service/daemon.js:608 msgid "Additional Software Required" msgstr "" #: src/service/daemon.js:617 #, javascript-format msgid "%s Plugin Failed To Load" msgstr "" #: src/service/daemon.js:618 src/service/daemon.js:635 msgid "Click for more information" msgstr "" #: src/service/daemon.js:633 msgid "Wayland Not Supported" msgstr "" #: src/service/daemon.js:634 msgid "Remote input not supported on Wayland" msgstr "" #. Create an urgent notification #: src/service/daemon.js:658 #, fuzzy, javascript-format msgid "GSConnect: %s" msgstr "GSConnect" #. TRANSLATORS: Share URL by SMS #: src/service/daemon.js:808 src/service/plugins/sms.js:49 #: webextension/gettext.js:39 msgid "Send SMS" msgstr "Poslat SMS" #: src/service/daemon.js:812 #, fuzzy msgid "Dial Number" msgstr "Vytočit telefonní číslo" #: src/service/device.js:166 #, fuzzy msgid "Not available" msgstr "Gvc není k dispozici" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:170 #, javascript-format msgid "Bluetooth device at %s" msgstr "" #. TRANSLATORS: Label for TLS Certificate fingerprint #. #. Example: #. #. Google Pixel Fingerprint: #. 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 #: src/service/device.js:188 src/service/device.js:190 #, javascript-format msgid "%s Fingerprint:" msgstr "" #: src/service/device.js:240 msgid "Laptop" msgstr "Notebook" #: src/service/device.js:242 msgid "Smartphone" msgstr "Chytrý telefon" #: src/service/device.js:244 msgid "Tablet" msgstr "Tablet" #: src/service/device.js:246 msgid "Desktop" msgstr "Počítač" #: src/service/device.js:409 #, fuzzy msgid "Reconnect" msgstr "GSConnect" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:606 #, javascript-format msgid "Pair Request from %s" msgstr "Žádost o spárování od" #: src/service/device.js:613 msgid "Reject" msgstr "Odmítnout" #: src/service/device.js:618 msgid "Accept" msgstr "Přimout" #: src/service/plugins/battery.js:194 src/service/plugins/findmyphone.js:18 #, fuzzy msgid "Ring" msgstr "Najít" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:203 #, javascript-format msgid "%s: Battery is low" msgstr "" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:205 #, fuzzy, javascript-format msgid "%d%% remaining" msgstr "%d%% (%d∶%02d zbývající)" #: src/service/plugins/clipboard.js:11 msgid "Clipboard" msgstr "Schránka" #: src/service/plugins/clipboard.js:17 #, fuzzy msgid "Clipboard Push" msgstr "Schránka" #: src/service/plugins/clipboard.js:25 #, fuzzy msgid "Clipboard Pull" msgstr "Schránka" #: src/service/plugins/contacts.js:12 #, fuzzy msgid "Contacts" msgstr "Neznámý kontakt" #: src/service/plugins/findmyphone.js:12 msgid "Find My Phone" msgstr "" #: src/service/plugins/findmyphone.js:65 msgid "Locate Device" msgstr "Najít zařízení" #: src/service/plugins/findmyphone.js:66 #, javascript-format msgid "%s asked to locate this device" msgstr "%s chce najít toto zařízení" #: src/service/plugins/findmyphone.js:74 msgid "Found" msgstr "Nalezeno" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "" #: src/service/plugins/mousepad.js:28 src/service/plugins/mousepad.js:578 #, fuzzy msgid "Keyboard" msgstr "Klávesové zkratky" #: src/service/plugins/mousepad.js:595 #, fuzzy msgid "Keyboard not ready" msgstr "Klávesové zkratky" #: src/service/plugins/mpris.js:10 msgid "MPRIS" msgstr "" #: src/service/plugins/notification.js:25 #, fuzzy msgid "Cancel Notification" msgstr "Poslat upozornění" #: src/service/plugins/notification.js:33 #, fuzzy msgid "Close Notification" msgstr "Poslat upozornění" #: src/service/plugins/notification.js:41 #, fuzzy msgid "Reply Notification" msgstr "Poslat upozornění" #: src/service/plugins/notification.js:49 #, fuzzy msgid "Send Notification" msgstr "Poslat upozornění" #: src/service/plugins/ping.js:11 src/service/plugins/ping.js:17 #: src/service/plugins/ping.js:46 msgid "Ping" 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:53 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/runcommand.js:12 msgid "Run Commands" msgstr "Spustit příkaz" #: src/service/plugins/sftp.js:12 msgid "SFTP" msgstr "" #: src/service/plugins/sftp.js:18 #, fuzzy msgid "Mount" msgstr "Odpojit" #: src/service/plugins/sftp.js:26 msgid "Unmount" msgstr "Odpojit" #: src/service/plugins/sftp.js:170 msgid "All files" msgstr "" #: src/service/plugins/sftp.js:171 msgid "Camera pictures" msgstr "" #: src/service/plugins/sftp.js:333 msgid "Files" msgstr "" #: src/service/plugins/share.js:12 src/service/plugins/share.js:18 msgid "Share" msgstr "Sdílet" #: src/service/plugins/share.js:26 #, fuzzy msgid "Share File" msgstr "Sdílet soubor/URL" #: src/service/plugins/share.js:34 #, fuzzy msgid "Share Text" msgstr "Sdílet" #: src/service/plugins/share.js:42 src/service/ui/messaging.js:903 #: src/service/ui/messaging.js:911 msgid "Share Link" msgstr "Sdílet odkaz" #: src/service/plugins/share.js:131 src/service/plugins/share.js:282 msgid "Starting Transfer" msgstr "Začíná přenos" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:133 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Získávání „%s“ z %s" #. Action Buttons #: src/service/plugins/share.js:138 src/service/plugins/share.js:289 #: src/service/plugins/share.js:407 src/service/ui/keybindings.js:44 #: src/service/ui/service.js:121 src/service/ui/service.js:300 #: src/service/ui/settings.js:873 src/shell/donotdisturb.js:215 msgid "Cancel" msgstr "Zrušit" #: src/service/plugins/share.js:149 src/service/plugins/share.js:303 msgid "Transfer Successful" msgstr "Přenos byl úspěšný" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:151 #, javascript-format msgid "Received “%s” from %s" msgstr "Získáno „%s“ z %s" #: src/service/plugins/share.js:157 msgid "Open Folder" msgstr "Otevřít složku" #: src/service/plugins/share.js:162 msgid "Open File" msgstr "Otevřít soubor" #: src/service/plugins/share.js:169 src/service/plugins/share.js:311 msgid "Transfer Failed" msgstr "Přenos se nezdařil" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:171 #, fuzzy, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Nepodařilo se získat „%s“ z %s: %s" #: src/service/plugins/share.js:211 #, javascript-format msgid "Text Shared By %s" msgstr "" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:284 #, 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:305 #, javascript-format msgid "Sent “%s” to %s" msgstr "Odesláno „%s“ do %s" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:313 #, fuzzy, javascript-format msgid "Failed to send “%s” to %s" msgstr "Nepodařilo se poslat „%s“ do %s: %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:384 #, javascript-format msgid "Send files to %s" msgstr "Poslat soubory do %s" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:402 #, javascript-format msgid "Send a link to %s" msgstr "Poslat odkaz do %s" #: src/service/plugins/share.js:408 msgid "Send" msgstr "Poslat" #: src/service/plugins/sms.js:12 msgid "SMS" msgstr "" #: src/service/plugins/sms.js:33 msgid "New SMS (URI)" msgstr "" #: src/service/plugins/sms.js:41 msgid "Reply SMS" msgstr "" #: src/service/plugins/sms.js:57 #, fuzzy msgid "Share SMS" msgstr "Sdílet" #: src/service/plugins/systemvolume.js:11 msgid "System Volume" msgstr "Hlasitost systému" #: src/service/plugins/telephony.js:21 #, fuzzy msgid "Mute Call" msgstr "Nepřijatý hovor" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #: src/service/plugins/telephony.js:155 src/service/ui/contacts.js:96 #: src/service/ui/contacts.js:350 msgid "Unknown Contact" msgstr "Neznámý kontakt" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:172 #, fuzzy msgid "Incoming call" msgstr "Příchozí hovor" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:187 #, fuzzy msgid "Ongoing call" msgstr "Příchozí hovor" #. TRANSLATORS: All other phone number types #: src/service/ui/contacts.js:58 src/service/ui/contacts.js:79 #, fuzzy, javascript-format msgid "%s・Other" msgstr "%s・Ostatní" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:63 #, javascript-format msgid "%s・Fax" msgstr "%s・Fax" #. TRANSLATORS: A work phone number #: src/service/ui/contacts.js:67 #, fuzzy, javascript-format msgid "%s・Work" msgstr "%s・Práce" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:71 #, fuzzy, javascript-format msgid "%s・Mobile" msgstr "%s・Mobilní" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:75 #, fuzzy, javascript-format msgid "%s・Home" msgstr "%s・Domov" #: src/service/ui/contacts.js:203 msgid "Select a contact or number" msgstr "" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #: src/service/ui/contacts.js:239 src/service/ui/contacts.js:253 #, javascript-format msgid "Send to %s" msgstr "Poslat do %s" #. TRANSLATORS: Title of keyboard shortcut dialog #: src/service/ui/keybindings.js:33 #, fuzzy msgid "Set Shortcut" msgstr "Klávesové zkratky" #. TRANSLATORS: Button to confirm the new shortcut #: src/service/ui/keybindings.js:47 #, fuzzy msgid "Set" msgstr "Vybrat" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/service/ui/keybindings.js:54 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "" #. TRANSLATORS: Keys for cancelling (␛) or resetting (␈) a shortcut #: src/service/ui/keybindings.js:83 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/service/ui/keybindings.js:182 #, javascript-format msgid "%s is already being used" msgstr "" #: src/service/ui/service.js:122 #, fuzzy msgid "Connect" msgstr "GSConnect" #: src/service/ui/service.js:294 msgid "Select a Device" msgstr "Vybrat zařízení" #: src/service/ui/service.js:298 msgid "Select" msgstr "Vybrat" #: src/service/ui/settings.js:298 msgid "Panel" msgstr "" #: src/service/ui/settings.js:298 #, fuzzy msgid "User Menu" msgstr "Otevřít Menu" #: src/service/ui/settings.js:874 #, fuzzy msgid "Open" msgstr "Otevřít soubor" #: src/service/ui/settings.js:931 src/service/ui/settings.js:944 msgid "On" msgstr "" #: src/service/ui/settings.js:931 src/service/ui/settings.js:944 msgid "Off" msgstr "" #: src/service/ui/settings.js:1065 src/service/ui/settings.js:1131 msgid "Disabled" msgstr "" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:93 src/service/ui/messaging.js:126 msgid "Just now" msgstr "" #. TRANSLATORS: Time duration in minutes (eg. 15 minutes) #: src/service/ui/messaging.js:98 src/service/ui/messaging.js:130 #: src/shell/donotdisturb.js:266 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "" msgstr[1] "" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:103 #, javascript-format msgid "Yesterday・%s" msgstr "" #: src/service/ui/messaging.js:920 msgid "New Message" msgstr "Nová zpráva" #. TRANSLATORS: When the battery level is 100% #: src/shell/device.js:86 msgid "Fully Charged" msgstr "Plně nabito" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:90 #, 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:100 #, 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:108 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d zbývající)" #: src/shell/donotdisturb.js:150 src/shell/donotdisturb.js:289 msgid "Do Not Disturb" msgstr "" #: src/shell/donotdisturb.js:156 #, fuzzy msgid "Silence Mobile Device Notifications" msgstr "Získat upozornění" #: src/shell/donotdisturb.js:171 msgid "Until you turn off Do Not Disturb" msgstr "" #. TRANSLATORS: Time until change with time duration #. EXAMPLE: Until 10:00 (2 hours) #: src/shell/donotdisturb.js:184 src/shell/donotdisturb.js:278 #, javascript-format msgid "Until %s (%s)" msgstr "" #: src/shell/donotdisturb.js:216 msgid "Done" msgstr "" #. TRANSLATORS: Time duration in hours (eg. 2 hours) #: src/shell/donotdisturb.js:263 #, javascript-format msgid "One hour" msgid_plural "%d hours" msgstr[0] "" msgstr[1] "" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:29 #, fuzzy msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "Sdílet odkazy se zařízeními a kontakty z webového prohlížeče" #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:33 #, fuzzy msgid "Service Unavailable" msgstr "Služba nedostupná" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:37 #, fuzzy msgid "Open in Browser" msgstr "Otevřít složku"gnome-shell-extension-gsconnect-20/po/de.po000066400000000000000000000577651341554142200210750ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-12-29 16:44-0800\n" "PO-Revision-Date: 2019-01-08 20:43\n" "Last-Translator: andyholmes \n" "Language-Team: German\n" "Language: 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-Generator: crowdin.com\n" "X-Crowdin-Project: gsconnect\n" "X-Crowdin-Language: de\n" "X-Crowdin-File: /master/po/org.gnome.Shell.Extensions.GSConnect.pot\n" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/connect.ui:24 data/menus.ui:7 msgid "Connect to…" msgstr "Verbinde mit…" #. Action Buttons #: data/connect.ui:30 data/notification.ui:14 data/telephony.ui:14 #: src/service/plugins/share.js:139 src/service/plugins/share.js:310 #: src/service/plugins/share.js:456 src/service/ui/device.js:607 #: src/service/ui/keybindings.js:44 src/service/ui/service.js:37 #: src/service/ui/settings.js:539 src/shell/donotdisturb.js:215 msgid "Cancel" msgstr "Abbruch" #: data/connect.ui:37 msgid "Connect" msgstr "Verbinden" #: data/connect.ui:98 msgid "IP Address" msgstr "IP-Adresse" #: data/connect.ui:142 msgid "Bluetooth Device" msgstr "Bluetooth-Gerät" #: data/contacts.ui:49 msgid "Type a phone number or name" msgstr "Telefonnummer oder Name eingeben" #: data/contacts.ui:83 msgid "No contacts" msgstr "Keine Kontakte" #: data/contacts.ui:95 data/menus.ui:33 data/messaging.ui:247 msgid "Help" msgstr "Hilfe" #: data/conversation.ui:76 data/conversation.ui:85 src/shell/notification.js:51 msgid "Type a message" msgstr "Verfasse eine Nachricht" #: data/conversation.ui:84 msgid "Send Message" msgstr "Nachricht schicken" #: data/device.ui:67 src/service/plugins/battery.js:12 msgid "Battery" msgstr "Akku" #: data/device.ui:122 msgid "Clipboard Sync" msgstr "Synchronisiere die Zwischenablage" #: data/device.ui:185 msgid "Media Players" msgstr "Medien Steuerung" #: data/device.ui:240 msgid "Mouse & Keyboard" msgstr "Maus & Tastatur" #: data/device.ui:295 msgid "Volume Control" msgstr "Lautstärken Kontrolle" #: data/device.ui:345 data/device.ui:1728 msgid "Sharing" msgstr "Teilen" #: data/device.ui:374 data/device.ui:651 data/device.ui:1774 #: src/service/plugins/runcommand.js:18 src/service/plugins/runcommand.js:169 msgid "Commands" msgstr "Befehle" #: data/device.ui:424 data/device.ui:427 msgid "Name" msgstr "Name" #: data/device.ui:440 data/device.ui:446 msgid "Command Line" msgstr "Kommandozeile" #: data/device.ui:444 data/device.ui:445 msgid "Choose an executable" msgstr "Wähle ein ausführbares Programm" #: data/device.ui:496 data/device.ui:511 msgid "Add" msgstr "Hinzufügen" #: data/device.ui:527 data/device.ui:542 msgid "Remove" msgstr "Entfernen" #: data/device.ui:576 data/device.ui:588 msgid "Edit" msgstr "Bearbeiten" #: data/device.ui:604 data/device.ui:616 msgid "Save" msgstr "Speichern" #: data/device.ui:712 msgid "Share Notifications" msgstr "Teile Benachrichtigungen" #: data/device.ui:763 msgid "Applications" msgstr "Anwendungen" #: data/device.ui:809 data/device.ui:1820 #: src/service/plugins/notification.js:13 msgid "Notifications" msgstr "Benachrichtigungen" #: data/device.ui:839 msgid "Incoming Calls" msgstr "Eingehende Anrufe" #: data/device.ui:888 data/device.ui:1055 msgid "Volume" msgstr "Lautstärke" #: data/device.ui:954 data/device.ui:1120 msgid "Pause Media" msgstr "Wiedergabe pausieren" #: data/device.ui:1007 msgid "Ongoing Calls" msgstr "Laufende Anrufe" #: data/device.ui:1176 msgid "Mute Microphone" msgstr "Mikrofon stummschalten" #: data/device.ui:1230 data/device.ui:1866 src/service/plugins/telephony.js:13 msgid "Telephony" msgstr "Telefonie" #: data/device.ui:1265 msgid "Action Shortcuts" msgstr "Aktions Tastenkürzel" #: data/device.ui:1280 data/device.ui:1349 msgid "Reset All…" msgstr "Setze alles zurück…" #: data/device.ui:1334 msgid "Command Shortcuts" msgstr "Befehlstastenkürzel" #: data/device.ui:1398 msgid "Shortcuts" msgstr "Tastenkürzel" #: data/device.ui:1429 msgid "Plugins" msgstr "Plugins" #: data/device.ui:1475 msgid "Experimental" msgstr "Experimentell" #: data/device.ui:1524 msgid "Legacy SMS Support" msgstr "Alte SMS-Unterstützung" #: data/device.ui:1598 msgid "Delete" msgstr "Löschen" #: data/device.ui:1627 msgid "Delete this device" msgstr "Lösche dieses Gerät" #: data/device.ui:1645 msgid "Unpair and remove all settings and files" msgstr "Trenne und entferne alle Einstellungen und Dateien" #: data/device.ui:1678 data/device.ui:1958 msgid "Advanced" msgstr "Erweitert" #: data/device.ui:1912 msgid "Keyboard Shortcuts" msgstr "Tastenkürzel" #. TRANSLATORS: Send a pair request to the device #: data/device.ui:2015 data/menus.ui:68 src/service/device.js:424 msgid "Pair" msgstr "Verbinde" #: data/device.ui:2047 msgid "Device is unpaired" msgstr "Gerät ist nicht gepaart" #: data/device.ui:2062 msgid "You may configure this device before pairing" msgstr "Sie können dieses Gerät vor der Paarung konfigurieren" #: data/menus.ui:12 msgid "Display Mode" msgstr "Anzeigemodus" #. TRANSLATORS: Show device indicators in the top bar #: data/menus.ui:15 msgid "Panel" msgstr "Panel" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/menus.ui:21 msgid "User Menu" msgstr "Benutzermenü" #. TRANSLATORS: Generate a support log #: data/menus.ui:29 src/service/ui/settings.js:536 msgid "Generate Support Log" msgstr "Support-Protokoll generieren" #: data/menus.ui:38 msgid "About" msgstr "Über" #. TRANSLATORS: Change the connection type to Bluetooth #: data/menus.ui:49 msgid "Switch to Bluetooth" msgstr "Wechseln Sie zu Bluetooth" #. TRANSLATORS: Change the connection type to TCP/IP #: data/menus.ui:56 msgid "Switch to LAN" msgstr "Wechseln Sie zu LAN" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/menus.ui:63 src/service/ui/device.js:317 msgid "Encryption Info" msgstr "Verschlüsselung Info" #. TRANSLATORS: Unpair the device and notify it #: data/menus.ui:74 src/service/device.js:432 msgid "Unpair" msgstr "Trenne" #. TRANSLATORS: Send clipboard content to device #: data/menus.ui:86 msgid "To Device" msgstr "Zum Gerät" #. TRANSLATORS: Receive clipboard content from the device #: data/menus.ui:92 msgid "From Device" msgstr "Vom Gerät" #. TRANSLATORS: Don't change the system volume #: data/menus.ui:104 data/menus.ui:130 msgid "Nothing" msgstr "Nichts" #. TRANSLATORS: Lower the system volume #: data/menus.ui:111 data/menus.ui:137 msgid "Lower" msgstr "Leiser" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the phone ringer #: data/menus.ui:118 data/menus.ui:144 src/service/plugins/telephony.js:194 msgid "Mute" msgstr "Stumm schalten" #: data/messaging.ui:12 src/service/plugins/sms.js:26 #: src/service/ui/messaging.js:911 msgid "Messaging" msgstr "Nachrichten" #: data/messaging.ui:21 src/service/ui/messaging.js:992 msgid "New Conversation" msgstr "Neues Gespräch" #: data/messaging.ui:110 msgid "No conversation selected" msgstr "Keine Unterhaltung ausgewählt" #: data/messaging.ui:126 msgid "Select or start a conversation" msgstr "Wähle oder Starte eine Unterhaltung" #: data/messaging.ui:193 data/notification.ui:52 data/telephony.ui:52 msgid "Device is disconnected" msgstr "Gerät ist getrennt" #: data/messaging.ui:264 msgid "No Conversations" msgstr "Keine Konversationen" #: data/notification.ui:21 data/telephony.ui:21 #: src/service/plugins/share.js:457 msgid "Send" msgstr "Senden" #: data/settings.ui:10 msgid "Searching for devices…" msgstr "Geräte werden gesucht…" #: data/settings.ui:49 data/settings.ui:63 msgid "Refresh" msgstr "Aktualisieren" #: data/settings.ui:74 data/settings.ui:91 src/extension.js:105 msgid "Mobile Settings" msgstr "Mobile Einstellungen" #: data/settings.ui:144 data/settings.ui:158 msgid "Edit Device Name" msgstr "Gerätename bearbeiten" #: data/settings.ui:236 msgid "Devices" msgstr "Geräte" #: data/settings.ui:299 msgid "Browser Add-Ons" msgstr "Browser Erweiterungen" #: data/settings.ui:579 msgid "Enable" msgstr "Aktivieren" #: data/settings.ui:611 msgid "This device is invisible to unpaired devices" msgstr "Dieses Gerät ist für ungepaarte Geräte unsichtbar" #: data/settings.ui:623 src/service/daemon.js:532 msgid "Discovery Disabled" msgstr "Suchen deaktiviert" #. TRANSLATORS: Share URL by SMS #: data/telephony.ui:9 src/service/daemon.js:718 src/service/plugins/sms.js:50 #: webextension/gettext.js:39 msgid "Send SMS" msgstr "SMS senden" #. Service Menu #: src/extension.js:79 src/extension.js:255 msgid "Mobile Devices" msgstr "Mobile Geräte" #. TRANSLATORS: %d is the number of devices connected #: src/extension.js:253 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d verbunden" msgstr[1] "%d verbunden" #. TRANSLATORS: Top-level context menu item for GSConnect #: src/nautilus-gsconnect.py:112 webextension/gettext.js:31 msgid "Send To Mobile Device" msgstr "An Mobilgerät senden" #: src/service/daemon.js:372 msgid "Report" msgstr "Bericht" #: src/service/daemon.js:507 msgid "Authentication Failure" msgstr "Authentifikationsfehler" #: src/service/daemon.js:516 msgid "Network Error" msgstr "Netzwerkfehler" #: src/service/daemon.js:517 src/service/daemon.js:525 msgid "Click for help troubleshooting" msgstr "Klicke um Hilfe zu bekommen" #: src/service/daemon.js:524 msgid "PulseAudio Error" msgstr "PulseAudio-Fehler" #: src/service/daemon.js:533 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "Suchen wurde deaktiviert aufgrund der Anzahl an Geräten in diesem Netzwerk." #: src/service/daemon.js:541 #, javascript-format msgid "%s Plugin Failed To Load" msgstr "%s Plugin konnte nicht geladen werden" #: src/service/daemon.js:542 msgid "Click for more information" msgstr "Klicke für mehr Informationen" #: src/service/daemon.js:724 msgid "Dial Number" msgstr "Nummer wählen" #: src/service/daemon.js:730 src/service/plugins/share.js:27 msgid "Share File" msgstr "Datei teilen" #: src/service/device.js:161 msgid "Not available" msgstr "Nicht verfügbar" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:165 #, javascript-format msgid "Bluetooth device at %s" msgstr "Bluetooth-Gerät bei %s" #. TRANSLATORS: Label for TLS Certificate fingerprint #. #. Example: #. #. Google Pixel Fingerprint: #. 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 #: src/service/device.js:183 src/service/device.js:185 #, javascript-format msgid "%s Fingerprint:" msgstr "%s Fingerabdruck:" #: src/service/device.js:242 msgid "Laptop" msgstr "Laptop" #: src/service/device.js:244 msgid "Smartphone" msgstr "Smartphone" #: src/service/device.js:246 msgid "Tablet" msgstr "Tablet" #: src/service/device.js:248 msgid "Desktop" msgstr "Desktop" #: src/service/device.js:408 src/service/ui/device.js:15 msgid "Reconnect" msgstr "Neuverbinden" #: src/service/device.js:416 src/service/ui/device.js:16 #: src/service/ui/settings.js:371 msgid "Settings" msgstr "Einstellungen" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:607 #, javascript-format msgid "Pair Request from %s" msgstr "Kopplung von %s angefordert" #: src/service/device.js:614 msgid "Reject" msgstr "Ablehnen" #: src/service/device.js:619 msgid "Accept" msgstr "Annehmen" #: src/service/plugins/battery.js:188 src/service/plugins/findmyphone.js:19 msgid "Ring" msgstr "Ring" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:197 #, javascript-format msgid "%s: Battery is low" msgstr "%s: Akku ist leer" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:199 #, javascript-format msgid "%d%% remaining" msgstr "%d%% verbleibend" #: src/service/plugins/clipboard.js:11 msgid "Clipboard" msgstr "Zwischenablage" #: src/service/plugins/clipboard.js:17 msgid "Clipboard Push" msgstr "Pushen der Zwischenablage" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Pull" msgstr "Ziehen der Zwischenablage" #: src/service/plugins/contacts.js:12 msgid "Contacts" msgstr "Kontakte" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #: src/service/plugins/contacts.js:263 src/service/plugins/telephony.js:173 #: src/service/plugins/telephony.js:224 src/service/plugins/telephony.js:264 #: src/service/ui/contacts.js:156 src/service/ui/contacts.js:397 msgid "Unknown Contact" msgstr "Unbekannter Kontakt" #: src/service/plugins/findmyphone.js:13 msgid "Find My Phone" msgstr "Finde mein Handy" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Mauspad" #: src/service/plugins/mousepad.js:28 src/service/plugins/mousepad.js:569 msgid "Keyboard" msgstr "Tastatur" #: src/service/plugins/mousepad.js:223 msgid "Additional Software Required" msgstr "Zusätzliche benötigte Software" #: src/service/plugins/mousepad.js:586 msgid "Keyboard not ready" msgstr "Tastatur nicht bereit" #: src/service/plugins/mpris.js:10 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/notification.js:26 msgid "Cancel Notification" msgstr "Benachrichtigung abbrechen" #: src/service/plugins/notification.js:34 msgid "Close Notification" msgstr "Benachrichtigung schließen" #: src/service/plugins/notification.js:42 msgid "Reply Notification" msgstr "Auf Benachrichtigung antworten" #: src/service/plugins/notification.js:50 msgid "Send Notification" msgstr "Benachrichtigung senden" #: src/service/plugins/ping.js:11 src/service/plugins/ping.js:17 #: src/service/plugins/ping.js:46 msgid "Ping" 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:53 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/runcommand.js:12 msgid "Run Commands" msgstr "Befehl ausführen" #: src/service/plugins/sftp.js:12 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:18 msgid "Mount" msgstr "Einhängen" #: src/service/plugins/sftp.js:26 msgid "Unmount" msgstr "Aushängen" #: src/service/plugins/sftp.js:159 msgid "All files" msgstr "Alle Dateien" #: src/service/plugins/sftp.js:160 msgid "Camera pictures" msgstr "Kamerabilder" #: src/service/plugins/sftp.js:417 msgid "Files" msgstr "Dateien" #: src/service/plugins/share.js:13 src/service/plugins/share.js:19 msgid "Share" msgstr "Teilen" #: src/service/plugins/share.js:35 msgid "Share Text" msgstr "Text teilen" #: src/service/plugins/share.js:43 src/service/ui/messaging.js:975 #: src/service/ui/messaging.js:983 msgid "Share Link" msgstr "Link teilen" #: src/service/plugins/share.js:132 src/service/plugins/share.js:303 msgid "Starting Transfer" msgstr "Übertragung starten" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:134 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Empfange „%s“ von %s" #: src/service/plugins/share.js:172 src/service/plugins/share.js:327 msgid "Transfer Successful" msgstr "Übertragung erfolgreich" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:174 #, javascript-format msgid "Received “%s” from %s" msgstr "„%s“ von %s empfangen" #: src/service/plugins/share.js:180 msgid "Open Folder" msgstr "Ordner öffnen" #: src/service/plugins/share.js:185 msgid "Open File" msgstr "Datei öffnen" #: src/service/plugins/share.js:192 src/service/plugins/share.js:335 msgid "Transfer Failed" msgstr "Übertragung gescheitert" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:194 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Empfang von „%s“ von %s gescheitert" #: src/service/plugins/share.js:233 #, javascript-format msgid "Text Shared By %s" msgstr "Text geteilt von %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:305 #, javascript-format msgid "Sending “%s” to %s" msgstr "„%s“ an %s senden" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:329 #, 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:337 #, 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:408 #, javascript-format msgid "Send files to %s" msgstr "Sende Dateien an %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:412 msgid "Open when done" msgstr "Öffnen nach Erledigung" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:451 #, javascript-format msgid "Send a link to %s" msgstr "Sende Link an %s" #: src/service/plugins/sms.js:13 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:34 msgid "New SMS (URI)" msgstr "Neue SMS (URI)" #: src/service/plugins/sms.js:42 src/service/plugins/telephony.js:22 msgid "Reply SMS" msgstr "Auf SMS antworten" #: src/service/plugins/sms.js:58 msgid "Share SMS" msgstr "SMS teilen" #: src/service/plugins/systemvolume.js:11 msgid "System Volume" msgstr "System Lautstärke" #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Stelle Anruf stumm" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:190 msgid "Incoming call" msgstr "Eingehender Anruf" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:205 msgid "Ongoing call" msgstr "Laufender Anruf" #. TRANSLATORS: All other phone number types #: src/service/ui/contacts.js:118 src/service/ui/contacts.js:139 #, javascript-format msgid "%s・Other" msgstr "%s・Andere" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:123 #, javascript-format msgid "%s・Fax" msgstr "%s・Fax" #. TRANSLATORS: A work phone number #: src/service/ui/contacts.js:127 #, javascript-format msgid "%s・Work" msgstr "%s・Arbeit" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:131 #, javascript-format msgid "%s・Mobile" msgstr "%s・Handy" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:135 #, javascript-format msgid "%s・Home" msgstr "%s・Haus" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #: src/service/ui/contacts.js:278 src/service/ui/contacts.js:292 #, javascript-format msgid "Send to %s" msgstr "An %s senden" #: src/service/ui/device.js:608 msgid "Open" msgstr "Öffnen" #: src/service/ui/device.js:660 src/service/ui/device.js:673 msgid "On" msgstr "An" #: src/service/ui/device.js:660 src/service/ui/device.js:673 msgid "Off" msgstr "Aus" #: src/service/ui/device.js:798 src/service/ui/device.js:826 #: src/service/ui/device.js:850 msgid "Disabled" msgstr "Deaktiviert" #. TRANSLATORS: Title of keyboard shortcut dialog #: src/service/ui/keybindings.js:33 msgid "Set Shortcut" msgstr "Setze Tastenkürzel" #. TRANSLATORS: Button to confirm the new shortcut #: src/service/ui/keybindings.js:47 msgid "Set" msgstr "Setze" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/service/ui/keybindings.js:54 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Gebe ein neues Tastenkürzel ein um %s zu Ändern" #. TRANSLATORS: Keys for cancelling (␛) or resetting (␈) a shortcut #: src/service/ui/keybindings.js:83 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "Drücke ESC zum Abbrechen oder die Rücktaste um das Tastenkürzel Zurückzusetzen." #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/service/ui/keybindings.js:182 #, javascript-format msgid "%s is already being used" msgstr "%s ist schon vergeben" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:65 src/service/ui/messaging.js:98 msgid "Just now" msgstr "Gerade jetzt" #. TRANSLATORS: Time duration in minutes (eg. 15 minutes) #: src/service/ui/messaging.js:70 src/service/ui/messaging.js:102 #: src/shell/donotdisturb.js:266 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d Minute" msgstr[1] "%d Minuten" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:75 #, javascript-format msgid "Yesterday・%s" msgstr "Gestern・%s" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:243 #, javascript-format msgid "You: %s" msgstr "Sie: %s" #: src/service/ui/service.js:31 msgid "Select a Device" msgstr "Gerät auswählen" #: src/service/ui/service.js:35 msgid "Select" msgstr "Auswählen" #: src/service/ui/settings.js:331 msgid "Unpaired" msgstr "Ungepaarte" #: src/service/ui/settings.js:333 msgid "Disconnected" msgstr "Getrennt" #: src/service/ui/settings.js:336 msgid "Connected" msgstr "Verbunden" #. TRANSLATORS: Description of where directly shared files are stored. #: src/service/ui/settings.js:406 #, javascript-format msgid "Transferred files are placed in the Downloads folder." msgstr "Übertragene Dateien befinden sich im Ordner \" Downloads\"." #: src/service/ui/settings.js:499 msgid "A complete KDE Connect implementation for GNOME" msgstr "Eine vollständige KDE Connect Implementierung für GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/service/ui/settings.js:508 msgid "translator-credits" msgstr "taaem " #: src/service/ui/settings.js:537 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "Debugmeldungen werden protokolliert. Führen Sie alle erforderlichen Schritte aus, um ein Problem zu reproduzieren, und überprüfen Sie das Protokoll." #: src/service/ui/settings.js:540 msgid "Review Log" msgstr "Protokoll überprüfen" #. TRANSLATORS: When the battery level is 100% #: src/shell/device.js:82 msgid "Fully Charged" msgstr "Vollständig geladen" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:86 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (Schätze..)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:96 #, javascript-format msgid "%d%% (%d∶%02d Until Full)" msgstr "%d%% (%d:%02d bis vollständig geladen)" #. TRANSLATORS: Estimated time until battery is empty #. EXAMPLE: 42% (12:15 Remaining) #: src/shell/device.js:104 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d verbleibend)" #: src/shell/donotdisturb.js:150 src/shell/donotdisturb.js:289 msgid "Do Not Disturb" msgstr "Nicht stören" #: src/shell/donotdisturb.js:156 msgid "Silence Mobile Device Notifications" msgstr "Stelle Mobile Benachrichtigungen stumm" #: src/shell/donotdisturb.js:171 msgid "Until you turn off Do Not Disturb" msgstr "Bis zum Ausschalten von \"Nicht stören\"" #. TRANSLATORS: Time until change with time duration #. EXAMPLE: Until 10:00 (2 hours) #: src/shell/donotdisturb.js:184 src/shell/donotdisturb.js:278 #, javascript-format msgid "Until %s (%s)" msgstr "Bis %s (%s)" #: src/shell/donotdisturb.js:216 msgid "Done" msgstr "Erledigt" #. TRANSLATORS: Time duration in hours (eg. 2 hours) #: src/shell/donotdisturb.js:263 #, javascript-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "%d stunde" msgstr[1] "%d stunden" #: src/shell/notification.js:42 msgid "Reply" msgstr "Antworten" #. TRANSLATORS: Extension name #: webextension/gettext.js:27 msgid "GSConnect" msgstr "GSConnect" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:29 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "Teile Links über GSConnect, direkt an den Browser oder per SMS." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:33 msgid "Service Unavailable" msgstr "Service nicht verfügbar" #. TRANSLATORS: No devices are known or available #: webextension/gettext.js:35 msgid "No Device Found" msgstr "Kein Gerät gefunden" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:37 msgid "Open in Browser" msgstr "Im Browser öffnen" gnome-shell-extension-gsconnect-20/po/es.po000066400000000000000000000605521341554142200211000ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-12-29 16:44-0800\n" "PO-Revision-Date: 2019-01-01 11:42\n" "Last-Translator: andyholmes \n" "Language-Team: Spanish\n" "Language: 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-Generator: crowdin.com\n" "X-Crowdin-Project: gsconnect\n" "X-Crowdin-Language: es-ES\n" "X-Crowdin-File: /master/po/org.gnome.Shell.Extensions.GSConnect.pot\n" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/connect.ui:24 data/menus.ui:7 msgid "Connect to…" msgstr "Conectar con…" #. Action Buttons #: data/connect.ui:30 data/notification.ui:14 data/telephony.ui:14 #: src/service/plugins/share.js:139 src/service/plugins/share.js:310 #: src/service/plugins/share.js:456 src/service/ui/device.js:607 #: src/service/ui/keybindings.js:44 src/service/ui/service.js:37 #: src/service/ui/settings.js:539 src/shell/donotdisturb.js:215 msgid "Cancel" msgstr "Cancelar" #: data/connect.ui:37 msgid "Connect" msgstr "Conectar" #: data/connect.ui:98 msgid "IP Address" msgstr "Dirección IP" #: data/connect.ui:142 msgid "Bluetooth Device" msgstr "Dispositivo Bluetooth" #: data/contacts.ui:49 msgid "Type a phone number or name" msgstr "Escriba un número telefónico o un nombre" #: data/contacts.ui:83 msgid "No contacts" msgstr "No hay ningún contacto" #: data/contacts.ui:95 data/menus.ui:33 data/messaging.ui:247 msgid "Help" msgstr "Ayuda" #: data/conversation.ui:76 data/conversation.ui:85 src/shell/notification.js:51 msgid "Type a message" msgstr "Escriba un mensaje" #: data/conversation.ui:84 msgid "Send Message" msgstr "Enviar mensaje" #: data/device.ui:67 src/service/plugins/battery.js:12 msgid "Battery" msgstr "Batería" #: data/device.ui:122 msgid "Clipboard Sync" msgstr "Sincronización de portapapeles" #: data/device.ui:185 msgid "Media Players" msgstr "Reproductores multimedia" #: data/device.ui:240 msgid "Mouse & Keyboard" msgstr "Ratón y teclado" #: data/device.ui:295 msgid "Volume Control" msgstr "Control de volumen" #: data/device.ui:345 data/device.ui:1728 msgid "Sharing" msgstr "Compartición" #: data/device.ui:374 data/device.ui:651 data/device.ui:1774 #: src/service/plugins/runcommand.js:18 src/service/plugins/runcommand.js:169 msgid "Commands" msgstr "Órdenes" #: data/device.ui:424 data/device.ui:427 msgid "Name" msgstr "Nombre" #: data/device.ui:440 data/device.ui:446 msgid "Command Line" msgstr "Línea de órdenes" #: data/device.ui:444 data/device.ui:445 msgid "Choose an executable" msgstr "Elija un ejecutable" #: data/device.ui:496 data/device.ui:511 msgid "Add" msgstr "Añadir" #: data/device.ui:527 data/device.ui:542 msgid "Remove" msgstr "Quitar" #: data/device.ui:576 data/device.ui:588 msgid "Edit" msgstr "Editar" #: data/device.ui:604 data/device.ui:616 msgid "Save" msgstr "Guardar" #: data/device.ui:712 msgid "Share Notifications" msgstr "Notificaciones de compartición" #: data/device.ui:763 msgid "Applications" msgstr "Aplicaciones" #: data/device.ui:809 data/device.ui:1820 #: src/service/plugins/notification.js:13 msgid "Notifications" msgstr "Notificaciones" #: data/device.ui:839 msgid "Incoming Calls" msgstr "Llamadas entrantes" #: data/device.ui:888 data/device.ui:1055 msgid "Volume" msgstr "Volumen" #: data/device.ui:954 data/device.ui:1120 msgid "Pause Media" msgstr "Pausar multimedia" #: data/device.ui:1007 msgid "Ongoing Calls" msgstr "Llamadas en curso" #: data/device.ui:1176 msgid "Mute Microphone" msgstr "Silenciar micrófono" #: data/device.ui:1230 data/device.ui:1866 src/service/plugins/telephony.js:13 msgid "Telephony" msgstr "Telefonía" #: data/device.ui:1265 msgid "Action Shortcuts" msgstr "Atajos de acciones" #: data/device.ui:1280 data/device.ui:1349 msgid "Reset All…" msgstr "Restablecer todo…" #: data/device.ui:1334 msgid "Command Shortcuts" msgstr "Atajos de órdenes" #: data/device.ui:1398 msgid "Shortcuts" msgstr "Atajos" #: data/device.ui:1429 msgid "Plugins" msgstr "Complementos" #: data/device.ui:1475 msgid "Experimental" msgstr "Experimentos" #: data/device.ui:1524 msgid "Legacy SMS Support" msgstr "Compatibilidad SMS heredada" #: data/device.ui:1598 msgid "Delete" msgstr "Eliminar" #: data/device.ui:1627 msgid "Delete this device" msgstr "Eliminar este dispositivo" #: data/device.ui:1645 msgid "Unpair and remove all settings and files" msgstr "Desemparejar y eliminar todos los archivos y las configuraciones" #: data/device.ui:1678 data/device.ui:1958 msgid "Advanced" msgstr "Avanzado" #: data/device.ui:1912 msgid "Keyboard Shortcuts" msgstr "Atajos de teclado" #. TRANSLATORS: Send a pair request to the device #: data/device.ui:2015 data/menus.ui:68 src/service/device.js:424 msgid "Pair" msgstr "Emparejamiento" #: data/device.ui:2047 msgid "Device is unpaired" msgstr "El dispositivo no está emparejado" #: data/device.ui:2062 msgid "You may configure this device before pairing" msgstr "Puede configurar este dispositivo antes de emparejarlo" #: data/menus.ui:12 msgid "Display Mode" msgstr "Modo de visualización" #. TRANSLATORS: Show device indicators in the top bar #: data/menus.ui:15 msgid "Panel" msgstr "Panel" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/menus.ui:21 msgid "User Menu" msgstr "Menú de usuario" #. TRANSLATORS: Generate a support log #: data/menus.ui:29 src/service/ui/settings.js:536 msgid "Generate Support Log" msgstr "Generar registro para asistencia" #: data/menus.ui:38 msgid "About" msgstr "Acerca de" #. TRANSLATORS: Change the connection type to Bluetooth #: data/menus.ui:49 msgid "Switch to Bluetooth" msgstr "Cambiar a Bluetooth" #. TRANSLATORS: Change the connection type to TCP/IP #: data/menus.ui:56 msgid "Switch to LAN" msgstr "Cambiar a LAN" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/menus.ui:63 src/service/ui/device.js:317 msgid "Encryption Info" msgstr "Información de cifrado" #. TRANSLATORS: Unpair the device and notify it #: data/menus.ui:74 src/service/device.js:432 msgid "Unpair" msgstr "Desemparejar" #. TRANSLATORS: Send clipboard content to device #: data/menus.ui:86 msgid "To Device" msgstr "Al dispositivo" #. TRANSLATORS: Receive clipboard content from the device #: data/menus.ui:92 msgid "From Device" msgstr "Del dispositivo" #. TRANSLATORS: Don't change the system volume #: data/menus.ui:104 data/menus.ui:130 msgid "Nothing" msgstr "Nada" #. TRANSLATORS: Lower the system volume #: data/menus.ui:111 data/menus.ui:137 msgid "Lower" msgstr "Disminuir" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the phone ringer #: data/menus.ui:118 data/menus.ui:144 src/service/plugins/telephony.js:194 msgid "Mute" msgstr "Silenciar" #: data/messaging.ui:12 src/service/plugins/sms.js:26 #: src/service/ui/messaging.js:911 msgid "Messaging" msgstr "Mensajería" #: data/messaging.ui:21 src/service/ui/messaging.js:992 msgid "New Conversation" msgstr "Conversación nueva" #: data/messaging.ui:110 msgid "No conversation selected" msgstr "No se seleccionó ninguna conversación" #: data/messaging.ui:126 msgid "Select or start a conversation" msgstr "Seleccione una conversación o inicie una" #: data/messaging.ui:193 data/notification.ui:52 data/telephony.ui:52 msgid "Device is disconnected" msgstr "El dispositivo está desconectado" #: data/messaging.ui:264 msgid "No Conversations" msgstr "No hay ninguna conversación" #: data/notification.ui:21 data/telephony.ui:21 #: src/service/plugins/share.js:457 msgid "Send" msgstr "Enviar" #: data/settings.ui:10 msgid "Searching for devices…" msgstr "Buscando dispositivos…" #: data/settings.ui:49 data/settings.ui:63 msgid "Refresh" msgstr "Actualizar" #: data/settings.ui:74 data/settings.ui:91 src/extension.js:105 msgid "Mobile Settings" msgstr "Configuración de móvil" #: data/settings.ui:144 data/settings.ui:158 msgid "Edit Device Name" msgstr "Editar nombre de dispositivo" #: data/settings.ui:236 msgid "Devices" msgstr "Dispositivos" #: data/settings.ui:299 msgid "Browser Add-Ons" msgstr "Complementos para navegadores" #: data/settings.ui:579 msgid "Enable" msgstr "Activar" #: data/settings.ui:611 msgid "This device is invisible to unpaired devices" msgstr "Este dispositivo es invisible para dispositivos no emparejados" #: data/settings.ui:623 src/service/daemon.js:532 msgid "Discovery Disabled" msgstr "Descubrimiento desactivado" #. TRANSLATORS: Share URL by SMS #: data/telephony.ui:9 src/service/daemon.js:718 src/service/plugins/sms.js:50 #: webextension/gettext.js:39 msgid "Send SMS" msgstr "Enviar SMS" #. Service Menu #: src/extension.js:79 src/extension.js:255 msgid "Mobile Devices" msgstr "Dispositivos móviles" #. TRANSLATORS: %d is the number of devices connected #: src/extension.js:253 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d conectado" msgstr[1] "%d conectados" #. TRANSLATORS: Top-level context menu item for GSConnect #: src/nautilus-gsconnect.py:112 webextension/gettext.js:31 msgid "Send To Mobile Device" msgstr "Enviar a dispositivo móvil" #: src/service/daemon.js:372 msgid "Report" msgstr "Informe" #: src/service/daemon.js:507 msgid "Authentication Failure" msgstr "Fallo de autenticación" #: src/service/daemon.js:516 msgid "Network Error" msgstr "Error de red" #: src/service/daemon.js:517 src/service/daemon.js:525 msgid "Click for help troubleshooting" msgstr "Pulse para obtener información de solución de problemas" #: src/service/daemon.js:524 msgid "PulseAudio Error" msgstr "Error de PulseAudio" #: src/service/daemon.js:533 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/daemon.js:541 #, javascript-format msgid "%s Plugin Failed To Load" msgstr "Falló la carga del complemento %s" #: src/service/daemon.js:542 msgid "Click for more information" msgstr "Pulse para más información" #: src/service/daemon.js:724 msgid "Dial Number" msgstr "Marcar número" #: src/service/daemon.js:730 src/service/plugins/share.js:27 msgid "Share File" msgstr "Compartir archivo" #: src/service/device.js:161 msgid "Not available" msgstr "No disponible" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:165 #, javascript-format msgid "Bluetooth device at %s" msgstr "Dispositivo Bluetooth en %s" #. TRANSLATORS: Label for TLS Certificate fingerprint #. #. Example: #. #. Google Pixel Fingerprint: #. 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 #: src/service/device.js:183 src/service/device.js:185 #, javascript-format msgid "%s Fingerprint:" msgstr "Huella digital de %s:" #: src/service/device.js:242 msgid "Laptop" msgstr "Equipo portátil" #: src/service/device.js:244 msgid "Smartphone" msgstr "Teléfono inteligente" #: src/service/device.js:246 msgid "Tablet" msgstr "Tableta" #: src/service/device.js:248 msgid "Desktop" msgstr "Equipo de escritorio" #: src/service/device.js:408 src/service/ui/device.js:15 msgid "Reconnect" msgstr "Volver a conectar" #: src/service/device.js:416 src/service/ui/device.js:16 #: src/service/ui/settings.js:371 msgid "Settings" msgstr "Configuración" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:607 #, javascript-format msgid "Pair Request from %s" msgstr "Solicitud de emparejamiento de %s" #: src/service/device.js:614 msgid "Reject" msgstr "Rechazar" #: src/service/device.js:619 msgid "Accept" msgstr "Aceptar" #: src/service/plugins/battery.js:188 src/service/plugins/findmyphone.js:19 msgid "Ring" msgstr "Timbrar" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:197 #, javascript-format msgid "%s: Battery is low" msgstr "%s: batería baja" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:199 #, javascript-format msgid "%d%% remaining" msgstr "%d %% restante" #: src/service/plugins/clipboard.js:11 msgid "Clipboard" msgstr "Portapapeles" #: src/service/plugins/clipboard.js:17 msgid "Clipboard Push" msgstr "Envío a portapapeles" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Pull" msgstr "Recepción desde portapapeles" #: src/service/plugins/contacts.js:12 msgid "Contacts" msgstr "Contactos" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #: src/service/plugins/contacts.js:263 src/service/plugins/telephony.js:173 #: src/service/plugins/telephony.js:224 src/service/plugins/telephony.js:264 #: src/service/ui/contacts.js:156 src/service/ui/contacts.js:397 msgid "Unknown Contact" msgstr "Contacto desconocido" #: src/service/plugins/findmyphone.js:13 msgid "Find My Phone" msgstr "Encontrar mi teléfono" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Mousepad" #: src/service/plugins/mousepad.js:28 src/service/plugins/mousepad.js:569 msgid "Keyboard" msgstr "Teclado" #: src/service/plugins/mousepad.js:223 msgid "Additional Software Required" msgstr "Se necesita software adicional" #: src/service/plugins/mousepad.js:586 msgid "Keyboard not ready" msgstr "El teclado no está preparado" #: src/service/plugins/mpris.js:10 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/notification.js:26 msgid "Cancel Notification" msgstr "Cancelar notificación" #: src/service/plugins/notification.js:34 msgid "Close Notification" msgstr "Cerrar notificación" #: src/service/plugins/notification.js:42 msgid "Reply Notification" msgstr "Notificación de respuesta" #: src/service/plugins/notification.js:50 msgid "Send Notification" msgstr "Enviar notificación" #: src/service/plugins/ping.js:11 src/service/plugins/ping.js:17 #: src/service/plugins/ping.js:46 msgid "Ping" msgstr "Prueba de conectividad" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:53 #, javascript-format msgid "Ping: %s" msgstr "Prueba de conectividad: %s" #: src/service/plugins/runcommand.js:12 msgid "Run Commands" msgstr "Ejecutar órdenes" #: src/service/plugins/sftp.js:12 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:18 msgid "Mount" msgstr "Montar" #: src/service/plugins/sftp.js:26 msgid "Unmount" msgstr "Desmontar" #: src/service/plugins/sftp.js:159 msgid "All files" msgstr "Todos los archivos" #: src/service/plugins/sftp.js:160 msgid "Camera pictures" msgstr "Fotografías de la cámara" #: src/service/plugins/sftp.js:417 msgid "Files" msgstr "Archivos" #: src/service/plugins/share.js:13 src/service/plugins/share.js:19 msgid "Share" msgstr "Compartición" #: src/service/plugins/share.js:35 msgid "Share Text" msgstr "Compartir texto" #: src/service/plugins/share.js:43 src/service/ui/messaging.js:975 #: src/service/ui/messaging.js:983 msgid "Share Link" msgstr "Compartir enlace" #: src/service/plugins/share.js:132 src/service/plugins/share.js:303 msgid "Starting Transfer" msgstr "Comenzando la transferencia" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:134 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Recibiendo «%s» de %s" #: src/service/plugins/share.js:172 src/service/plugins/share.js:327 msgid "Transfer Successful" msgstr "Transferencia exitosa" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:174 #, javascript-format msgid "Received “%s” from %s" msgstr "Se recibió «%s» de %s" #: src/service/plugins/share.js:180 msgid "Open Folder" msgstr "Abrir carpeta" #: src/service/plugins/share.js:185 msgid "Open File" msgstr "Abrir archivo" #: src/service/plugins/share.js:192 src/service/plugins/share.js:335 msgid "Transfer Failed" msgstr "Transferencia fallida" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:194 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Falló la recepción de «%s» desde %s" #: src/service/plugins/share.js:233 #, 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:305 #, 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:329 #, 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:337 #, 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:408 #, 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:412 msgid "Open when done" msgstr "Abrir al terminar" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:451 #, javascript-format msgid "Send a link to %s" msgstr "Enviar un enlace a %s" #: src/service/plugins/sms.js:13 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:34 msgid "New SMS (URI)" msgstr "SMS nuevo (URI)" #: src/service/plugins/sms.js:42 src/service/plugins/telephony.js:22 msgid "Reply SMS" msgstr "Responder a SMS" #: src/service/plugins/sms.js:58 msgid "Share SMS" msgstr "Compartir SMS" #: src/service/plugins/systemvolume.js:11 msgid "System Volume" msgstr "Volumen del sistema" #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Silenciar llamada" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:190 msgid "Incoming call" msgstr "Llamada entrante" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:205 msgid "Ongoing call" msgstr "Llamada en curso" #. TRANSLATORS: All other phone number types #: src/service/ui/contacts.js:118 src/service/ui/contacts.js:139 #, javascript-format msgid "%s・Other" msgstr "%s・Otro" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:123 #, javascript-format msgid "%s・Fax" msgstr "%s・Fax" #. TRANSLATORS: A work phone number #: src/service/ui/contacts.js:127 #, javascript-format msgid "%s・Work" msgstr "%s・Trabajo" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:131 #, javascript-format msgid "%s・Mobile" msgstr "%s・Móvil" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:135 #, javascript-format msgid "%s・Home" msgstr "%s・Residencial" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #: src/service/ui/contacts.js:278 src/service/ui/contacts.js:292 #, javascript-format msgid "Send to %s" msgstr "Enviar a %s" #: src/service/ui/device.js:608 msgid "Open" msgstr "Abrir" #: src/service/ui/device.js:660 src/service/ui/device.js:673 msgid "On" msgstr "Activado" #: src/service/ui/device.js:660 src/service/ui/device.js:673 msgid "Off" msgstr "Desactivado" #: src/service/ui/device.js:798 src/service/ui/device.js:826 #: src/service/ui/device.js:850 msgid "Disabled" msgstr "Desactivado" #. TRANSLATORS: Title of keyboard shortcut dialog #: src/service/ui/keybindings.js:33 msgid "Set Shortcut" msgstr "Establecer atajo" #. TRANSLATORS: Button to confirm the new shortcut #: src/service/ui/keybindings.js:47 msgid "Set" msgstr "Establecer" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/service/ui/keybindings.js:54 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Digite un atajo nuevo para cambiar %s" #. TRANSLATORS: Keys for cancelling (␛) or resetting (␈) a shortcut #: src/service/ui/keybindings.js:83 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." #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/service/ui/keybindings.js:182 #, javascript-format msgid "%s is already being used" msgstr "Ya está utilizándose %s" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:65 src/service/ui/messaging.js:98 msgid "Just now" msgstr "Ahora mismo" #. TRANSLATORS: Time duration in minutes (eg. 15 minutes) #: src/service/ui/messaging.js:70 src/service/ui/messaging.js:102 #: src/shell/donotdisturb.js:266 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minuto" msgstr[1] "%d minutos" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:75 #, javascript-format msgid "Yesterday・%s" msgstr "Ayer・%s" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:243 #, javascript-format msgid "You: %s" msgstr "Usted: %s" #: src/service/ui/service.js:31 msgid "Select a Device" msgstr "Seleccione un dispositivo" #: src/service/ui/service.js:35 msgid "Select" msgstr "Seleccionar" #: src/service/ui/settings.js:331 msgid "Unpaired" msgstr "Desemparejado" #: src/service/ui/settings.js:333 msgid "Disconnected" msgstr "Desconectado" #: src/service/ui/settings.js:336 msgid "Connected" msgstr "Conectado" #. TRANSLATORS: Description of where directly shared files are stored. #: src/service/ui/settings.js:406 #, javascript-format msgid "Transferred files are placed in the Downloads folder." msgstr "Los archivos transferidos se almacenan en la carpeta Descargas." #: src/service/ui/settings.js:499 msgid "A complete KDE Connect implementation for GNOME" msgstr "Una completa implementación de KDE Connect para GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/service/ui/settings.js:508 msgid "translator-credits" msgstr "Adolfo Jayme Barrientos , 2018-2019" #: src/service/ui/settings.js:537 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/service/ui/settings.js:540 msgid "Review Log" msgstr "Revisar registro" #. TRANSLATORS: When the battery level is 100% #: src/shell/device.js:82 msgid "Fully Charged" msgstr "Cargada completamente" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:86 #, 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:96 #, 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:104 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d %% (quedan %d∶%02d)" #: src/shell/donotdisturb.js:150 src/shell/donotdisturb.js:289 msgid "Do Not Disturb" msgstr "No molestar" #: src/shell/donotdisturb.js:156 msgid "Silence Mobile Device Notifications" msgstr "Silenciar notificaciones de dispositivo móvil" #: src/shell/donotdisturb.js:171 msgid "Until you turn off Do Not Disturb" msgstr "Hasta que desactive No molestar" #. TRANSLATORS: Time until change with time duration #. EXAMPLE: Until 10:00 (2 hours) #: src/shell/donotdisturb.js:184 src/shell/donotdisturb.js:278 #, javascript-format msgid "Until %s (%s)" msgstr "Hasta las %s (en %s)" #: src/shell/donotdisturb.js:216 msgid "Done" msgstr "Hecho" #. TRANSLATORS: Time duration in hours (eg. 2 hours) #: src/shell/donotdisturb.js:263 #, javascript-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "%d hora" msgstr[1] "%d horas" #: src/shell/notification.js:42 msgid "Reply" msgstr "Responder" #. TRANSLATORS: Extension name #: webextension/gettext.js:27 msgid "GSConnect" msgstr "GSConnect" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:29 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:33 msgid "Service Unavailable" msgstr "Servicio no disponible" #. TRANSLATORS: No devices are known or available #: webextension/gettext.js:35 msgid "No Device Found" msgstr "No se encontró ningún dispositivo" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:37 msgid "Open in Browser" msgstr "Abrir en el navegador" gnome-shell-extension-gsconnect-20/po/et.po000066400000000000000000000571431341554142200211030ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-12-29 16:44-0800\n" "PO-Revision-Date: 2018-12-31 11:42\n" "Last-Translator: andyholmes \n" "Language-Team: Estonian\n" "Language: et\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: crowdin.com\n" "X-Crowdin-Project: gsconnect\n" "X-Crowdin-Language: et\n" "X-Crowdin-File: /master/po/org.gnome.Shell.Extensions.GSConnect.pot\n" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/connect.ui:24 data/menus.ui:7 msgid "Connect to…" msgstr "Ühendu…" #. Action Buttons #: data/connect.ui:30 data/notification.ui:14 data/telephony.ui:14 #: src/service/plugins/share.js:139 src/service/plugins/share.js:310 #: src/service/plugins/share.js:456 src/service/ui/device.js:607 #: src/service/ui/keybindings.js:44 src/service/ui/service.js:37 #: src/service/ui/settings.js:539 src/shell/donotdisturb.js:215 msgid "Cancel" msgstr "Loobu" #: data/connect.ui:37 msgid "Connect" msgstr "Ühenda" #: data/connect.ui:98 msgid "IP Address" msgstr "IP-aadress" #: data/connect.ui:142 msgid "Bluetooth Device" msgstr "Bluetooth-seade" #: data/contacts.ui:49 msgid "Type a phone number or name" msgstr "Kirjuta telefoninumber või nimi" #: data/contacts.ui:83 msgid "No contacts" msgstr "Kontakte pole" #: data/contacts.ui:95 data/menus.ui:33 data/messaging.ui:247 msgid "Help" msgstr "Abi" #: data/conversation.ui:76 data/conversation.ui:85 src/shell/notification.js:51 msgid "Type a message" msgstr "Kirjuta sõnum" #: data/conversation.ui:84 msgid "Send Message" msgstr "Saada sõnum" #: data/device.ui:67 src/service/plugins/battery.js:12 msgid "Battery" msgstr "Aku" #: data/device.ui:122 msgid "Clipboard Sync" msgstr "Lõikelaua sünkroonimine" #: data/device.ui:185 msgid "Media Players" msgstr "Meediamängijad" #: data/device.ui:240 msgid "Mouse & Keyboard" msgstr "Hiir ja klaviatuur" #: data/device.ui:295 msgid "Volume Control" msgstr "Helitugevuse juhtimine" #: data/device.ui:345 data/device.ui:1728 msgid "Sharing" msgstr "Jagamine" #: data/device.ui:374 data/device.ui:651 data/device.ui:1774 #: src/service/plugins/runcommand.js:18 src/service/plugins/runcommand.js:169 msgid "Commands" msgstr "Käsklused" #: data/device.ui:424 data/device.ui:427 msgid "Name" msgstr "Nimi" #: data/device.ui:440 data/device.ui:446 msgid "Command Line" msgstr "Käsurida" #: data/device.ui:444 data/device.ui:445 msgid "Choose an executable" msgstr "Vali käivitatav" #: data/device.ui:496 data/device.ui:511 msgid "Add" msgstr "Lisa" #: data/device.ui:527 data/device.ui:542 msgid "Remove" msgstr "Eemalda" #: data/device.ui:576 data/device.ui:588 msgid "Edit" msgstr "Muuda" #: data/device.ui:604 data/device.ui:616 msgid "Save" msgstr "Salvesta" #: data/device.ui:712 msgid "Share Notifications" msgstr "Jaga teateid" #: data/device.ui:763 msgid "Applications" msgstr "Rakendused" #: data/device.ui:809 data/device.ui:1820 #: src/service/plugins/notification.js:13 msgid "Notifications" msgstr "Teated" #: data/device.ui:839 msgid "Incoming Calls" msgstr "Sissetulevad kõned" #: data/device.ui:888 data/device.ui:1055 msgid "Volume" msgstr "Helitugevus" #: data/device.ui:954 data/device.ui:1120 msgid "Pause Media" msgstr "Peata meedia" #: data/device.ui:1007 msgid "Ongoing Calls" msgstr "Käimasolevad kõned" #: data/device.ui:1176 msgid "Mute Microphone" msgstr "Vaigista mikrofon" #: data/device.ui:1230 data/device.ui:1866 src/service/plugins/telephony.js:13 msgid "Telephony" msgstr "Telefonifunktsioon" #: data/device.ui:1265 msgid "Action Shortcuts" msgstr "Tegevuste otseteed" #: data/device.ui:1280 data/device.ui:1349 msgid "Reset All…" msgstr "Lähtesta kõik…" #: data/device.ui:1334 msgid "Command Shortcuts" msgstr "Käskluste otseteed" #: data/device.ui:1398 msgid "Shortcuts" msgstr "Otseteed" #: data/device.ui:1429 msgid "Plugins" msgstr "Pluginad" #: data/device.ui:1475 msgid "Experimental" msgstr "Katseline" #: data/device.ui:1524 msgid "Legacy SMS Support" msgstr "Pärand SMS-tugi" #: data/device.ui:1598 msgid "Delete" msgstr "Kustuta" #: data/device.ui:1627 msgid "Delete this device" msgstr "Kustuta see seade" #: data/device.ui:1645 msgid "Unpair and remove all settings and files" msgstr "Eemalda paardumine ja kustuta kõik seadistused ning failid" #: data/device.ui:1678 data/device.ui:1958 msgid "Advanced" msgstr "Täpsemad" #: data/device.ui:1912 msgid "Keyboard Shortcuts" msgstr "Klaviatuuriotseteed" #. TRANSLATORS: Send a pair request to the device #: data/device.ui:2015 data/menus.ui:68 src/service/device.js:424 msgid "Pair" msgstr "Paarita" #: data/device.ui:2047 msgid "Device is unpaired" msgstr "Seade on paaritamata" #: data/device.ui:2062 msgid "You may configure this device before pairing" msgstr "Sa võid seda seadet enne paaritamist seadistada" #: data/menus.ui:12 msgid "Display Mode" msgstr "Ekraanirežiim" #. TRANSLATORS: Show device indicators in the top bar #: data/menus.ui:15 msgid "Panel" msgstr "Paneel" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/menus.ui:21 msgid "User Menu" msgstr "Kasutajamenüü" #. TRANSLATORS: Generate a support log #: data/menus.ui:29 src/service/ui/settings.js:536 msgid "Generate Support Log" msgstr "Genereeri tugiteenusele logi" #: data/menus.ui:38 msgid "About" msgstr "Teave" #. TRANSLATORS: Change the connection type to Bluetooth #: data/menus.ui:49 msgid "Switch to Bluetooth" msgstr "Lülitu Bluetoothile" #. TRANSLATORS: Change the connection type to TCP/IP #: data/menus.ui:56 msgid "Switch to LAN" msgstr "Lülitu LANile" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/menus.ui:63 src/service/ui/device.js:317 msgid "Encryption Info" msgstr "Krüpteeringu teave" #. TRANSLATORS: Unpair the device and notify it #: data/menus.ui:74 src/service/device.js:432 msgid "Unpair" msgstr "Eemalda paardumine" #. TRANSLATORS: Send clipboard content to device #: data/menus.ui:86 msgid "To Device" msgstr "Seadmesse" #. TRANSLATORS: Receive clipboard content from the device #: data/menus.ui:92 msgid "From Device" msgstr "Seadmest" #. TRANSLATORS: Don't change the system volume #: data/menus.ui:104 data/menus.ui:130 msgid "Nothing" msgstr "Puudub" #. TRANSLATORS: Lower the system volume #: data/menus.ui:111 data/menus.ui:137 msgid "Lower" msgstr "Vaiksem" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the phone ringer #: data/menus.ui:118 data/menus.ui:144 src/service/plugins/telephony.js:194 msgid "Mute" msgstr "Vaigista" #: data/messaging.ui:12 src/service/plugins/sms.js:26 #: src/service/ui/messaging.js:911 msgid "Messaging" msgstr "Sõnumside" #: data/messaging.ui:21 src/service/ui/messaging.js:992 msgid "New Conversation" msgstr "Uus vestlus" #: data/messaging.ui:110 msgid "No conversation selected" msgstr "Ühtegi vestlust pole valitud" #: data/messaging.ui:126 msgid "Select or start a conversation" msgstr "Vali või alusta vestlus" #: data/messaging.ui:193 data/notification.ui:52 data/telephony.ui:52 msgid "Device is disconnected" msgstr "Seade on lahti ühendatud" #: data/messaging.ui:264 msgid "No Conversations" msgstr "Vestlused puuduvad" #: data/notification.ui:21 data/telephony.ui:21 #: src/service/plugins/share.js:457 msgid "Send" msgstr "Saada" #: data/settings.ui:10 msgid "Searching for devices…" msgstr "Seadmete otsimine…" #: data/settings.ui:49 data/settings.ui:63 msgid "Refresh" msgstr "Värskenda" #: data/settings.ui:74 data/settings.ui:91 src/extension.js:105 msgid "Mobile Settings" msgstr "Mobiiliseaded" #: data/settings.ui:144 data/settings.ui:158 msgid "Edit Device Name" msgstr "Muuda seadme nime" #: data/settings.ui:236 msgid "Devices" msgstr "Seadmed" #: data/settings.ui:299 msgid "Browser Add-Ons" msgstr "Brauserilaiendused" #: data/settings.ui:579 msgid "Enable" msgstr "Luba" #: data/settings.ui:611 msgid "This device is invisible to unpaired devices" msgstr "See seade on paardumata seadmetele nähtamatu" #: data/settings.ui:623 src/service/daemon.js:532 msgid "Discovery Disabled" msgstr "Avastamine keelatud" #. TRANSLATORS: Share URL by SMS #: data/telephony.ui:9 src/service/daemon.js:718 src/service/plugins/sms.js:50 #: webextension/gettext.js:39 msgid "Send SMS" msgstr "Saada SMS" #. Service Menu #: src/extension.js:79 src/extension.js:255 msgid "Mobile Devices" msgstr "Mobiilseadmed" #. TRANSLATORS: %d is the number of devices connected #: src/extension.js:253 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d ühendatud" msgstr[1] "%d ühendatud" #. TRANSLATORS: Top-level context menu item for GSConnect #: src/nautilus-gsconnect.py:112 webextension/gettext.js:31 msgid "Send To Mobile Device" msgstr "Saada mobiilseadmesse" #: src/service/daemon.js:372 msgid "Report" msgstr "Teata" #: src/service/daemon.js:507 msgid "Authentication Failure" msgstr "Autentimise viga" #: src/service/daemon.js:516 msgid "Network Error" msgstr "Võrguviga" #: src/service/daemon.js:517 src/service/daemon.js:525 msgid "Click for help troubleshooting" msgstr "Klõpsa, et saada veaotsingul abi" #: src/service/daemon.js:524 msgid "PulseAudio Error" msgstr "PulseAudio viga" #: src/service/daemon.js:533 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/daemon.js:541 #, javascript-format msgid "%s Plugin Failed To Load" msgstr "%s plugina laadimine ebaõnnestus" #: src/service/daemon.js:542 msgid "Click for more information" msgstr "Klõpsa rohkema teabe saamiseks" #: src/service/daemon.js:724 msgid "Dial Number" msgstr "Helista telefoninumbrile" #: src/service/daemon.js:730 src/service/plugins/share.js:27 msgid "Share File" msgstr "Jaga faili" #: src/service/device.js:161 msgid "Not available" msgstr "Pole saadaval" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:165 #, javascript-format msgid "Bluetooth device at %s" msgstr "Bluetooth-seade aadressil %s" #. TRANSLATORS: Label for TLS Certificate fingerprint #. #. Example: #. #. Google Pixel Fingerprint: #. 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 #: src/service/device.js:183 src/service/device.js:185 #, javascript-format msgid "%s Fingerprint:" msgstr "%s sõrmejälg:" #: src/service/device.js:242 msgid "Laptop" msgstr "Sülearvuti" #: src/service/device.js:244 msgid "Smartphone" msgstr "Nutitelefon" #: src/service/device.js:246 msgid "Tablet" msgstr "Tahvelarvuti" #: src/service/device.js:248 msgid "Desktop" msgstr "Lauaarvuti" #: src/service/device.js:408 src/service/ui/device.js:15 msgid "Reconnect" msgstr "Taasühenda" #: src/service/device.js:416 src/service/ui/device.js:16 #: src/service/ui/settings.js:371 msgid "Settings" msgstr "Seaded" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:607 #, javascript-format msgid "Pair Request from %s" msgstr "Paaritamistaotlus seadmelt %s" #: src/service/device.js:614 msgid "Reject" msgstr "Keeldu" #: src/service/device.js:619 msgid "Accept" msgstr "Nõustu" #: src/service/plugins/battery.js:188 src/service/plugins/findmyphone.js:19 msgid "Ring" msgstr "Helise" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:197 #, javascript-format msgid "%s: Battery is low" msgstr "%s: aku on tühi" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:199 #, javascript-format msgid "%d%% remaining" msgstr "%d%% jäänud" #: src/service/plugins/clipboard.js:11 msgid "Clipboard" msgstr "Lõikelaud" #: src/service/plugins/clipboard.js:17 msgid "Clipboard Push" msgstr "Lõikelaua saatmine" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Pull" msgstr "Lõikelaua vastuvõtmine" #: src/service/plugins/contacts.js:12 msgid "Contacts" msgstr "Kontaktid" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #: src/service/plugins/contacts.js:263 src/service/plugins/telephony.js:173 #: src/service/plugins/telephony.js:224 src/service/plugins/telephony.js:264 #: src/service/ui/contacts.js:156 src/service/ui/contacts.js:397 msgid "Unknown Contact" msgstr "Tundmatu kontakt" #: src/service/plugins/findmyphone.js:13 msgid "Find My Phone" msgstr "Leia mu telefon" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Hiirepadi" #: src/service/plugins/mousepad.js:28 src/service/plugins/mousepad.js:569 msgid "Keyboard" msgstr "Klaviatuur" #: src/service/plugins/mousepad.js:223 msgid "Additional Software Required" msgstr "Lisatarkvara on nõutud" #: src/service/plugins/mousepad.js:586 msgid "Keyboard not ready" msgstr "Klaviatuur pole valmis" #: src/service/plugins/mpris.js:10 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/notification.js:26 msgid "Cancel Notification" msgstr "Tühista teade" #: src/service/plugins/notification.js:34 msgid "Close Notification" msgstr "Sulge teade" #: src/service/plugins/notification.js:42 msgid "Reply Notification" msgstr "Vasta teatele" #: src/service/plugins/notification.js:50 msgid "Send Notification" msgstr "Saada teade" #: src/service/plugins/ping.js:11 src/service/plugins/ping.js:17 #: src/service/plugins/ping.js:46 msgid "Ping" 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:53 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/runcommand.js:12 msgid "Run Commands" msgstr "Käivita käsklusi" #: src/service/plugins/sftp.js:12 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:18 msgid "Mount" msgstr "Haagi" #: src/service/plugins/sftp.js:26 msgid "Unmount" msgstr "Haagi lahti" #: src/service/plugins/sftp.js:159 msgid "All files" msgstr "Kõik failid" #: src/service/plugins/sftp.js:160 msgid "Camera pictures" msgstr "Kaamera pildid" #: src/service/plugins/sftp.js:417 msgid "Files" msgstr "Failid" #: src/service/plugins/share.js:13 src/service/plugins/share.js:19 msgid "Share" msgstr "Jaga" #: src/service/plugins/share.js:35 msgid "Share Text" msgstr "Jaga teksti" #: src/service/plugins/share.js:43 src/service/ui/messaging.js:975 #: src/service/ui/messaging.js:983 msgid "Share Link" msgstr "Jaga linki" #: src/service/plugins/share.js:132 src/service/plugins/share.js:303 msgid "Starting Transfer" msgstr "Alustan ülekannet" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:134 #, javascript-format msgid "Receiving “%s” from %s" msgstr "„%s“vastuvõtmine seadmelt %s" #: src/service/plugins/share.js:172 src/service/plugins/share.js:327 msgid "Transfer Successful" msgstr "Ülekanne edukas" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:174 #, javascript-format msgid "Received “%s” from %s" msgstr "„%s“vastu võetud seadmelt %s" #: src/service/plugins/share.js:180 msgid "Open Folder" msgstr "Ava kataloog" #: src/service/plugins/share.js:185 msgid "Open File" msgstr "Ava fail" #: src/service/plugins/share.js:192 src/service/plugins/share.js:335 msgid "Transfer Failed" msgstr "Ülekanne ebaõnnestus" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:194 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "„%s“vastuvõtmine seadmest %s ebaõnnestus" #: src/service/plugins/share.js:233 #, 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:305 #, 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:329 #, 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:337 #, 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:408 #, 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:412 msgid "Open when done" msgstr "Ava, kui valmis" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:451 #, javascript-format msgid "Send a link to %s" msgstr "Saada link seadmesse %s" #: src/service/plugins/sms.js:13 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:34 msgid "New SMS (URI)" msgstr "Uus SMS (URI)" #: src/service/plugins/sms.js:42 src/service/plugins/telephony.js:22 msgid "Reply SMS" msgstr "Vasta SMSile" #: src/service/plugins/sms.js:58 msgid "Share SMS" msgstr "Jaga SMSi" #: src/service/plugins/systemvolume.js:11 msgid "System Volume" msgstr "Süsteemi helitugevus" #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Vaigista kõne" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:190 msgid "Incoming call" msgstr "Sissetulev kõne" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:205 msgid "Ongoing call" msgstr "Väljuv kõne" #. TRANSLATORS: All other phone number types #: src/service/ui/contacts.js:118 src/service/ui/contacts.js:139 #, javascript-format msgid "%s・Other" msgstr "%s・Muu" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:123 #, javascript-format msgid "%s・Fax" msgstr "%s・Faks" #. TRANSLATORS: A work phone number #: src/service/ui/contacts.js:127 #, javascript-format msgid "%s・Work" msgstr "%s・Töö" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:131 #, javascript-format msgid "%s・Mobile" msgstr "%s・Mobiil" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:135 #, javascript-format msgid "%s・Home" msgstr "%s・Kodu" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #: src/service/ui/contacts.js:278 src/service/ui/contacts.js:292 #, javascript-format msgid "Send to %s" msgstr "Saada seadmesse %s" #: src/service/ui/device.js:608 msgid "Open" msgstr "Ava" #: src/service/ui/device.js:660 src/service/ui/device.js:673 msgid "On" msgstr "Sees" #: src/service/ui/device.js:660 src/service/ui/device.js:673 msgid "Off" msgstr "Väljas" #: src/service/ui/device.js:798 src/service/ui/device.js:826 #: src/service/ui/device.js:850 msgid "Disabled" msgstr "Keelatud" #. TRANSLATORS: Title of keyboard shortcut dialog #: src/service/ui/keybindings.js:33 msgid "Set Shortcut" msgstr "Määra otsetee" #. TRANSLATORS: Button to confirm the new shortcut #: src/service/ui/keybindings.js:47 msgid "Set" msgstr "Määra" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/service/ui/keybindings.js:54 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Sisesta %s muutmiseks uus otsetee" #. TRANSLATORS: Keys for cancelling (␛) or resetting (␈) a shortcut #: src/service/ui/keybindings.js:83 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "Vajuta tühistamiseks Esc või klaviatuuriotsetee lähtestamiseks Tagasilüke." #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/service/ui/keybindings.js:182 #, javascript-format msgid "%s is already being used" msgstr "%s on juba kasutusel" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:65 src/service/ui/messaging.js:98 msgid "Just now" msgstr "Praegu" #. TRANSLATORS: Time duration in minutes (eg. 15 minutes) #: src/service/ui/messaging.js:70 src/service/ui/messaging.js:102 #: src/shell/donotdisturb.js:266 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minut" msgstr[1] "%d minutit" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:75 #, javascript-format msgid "Yesterday・%s" msgstr "Eile・%s" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:243 #, javascript-format msgid "You: %s" msgstr "Sina: %s" #: src/service/ui/service.js:31 msgid "Select a Device" msgstr "Vali seade" #: src/service/ui/service.js:35 msgid "Select" msgstr "Vali" #: src/service/ui/settings.js:331 msgid "Unpaired" msgstr "Paaritamata" #: src/service/ui/settings.js:333 msgid "Disconnected" msgstr "Ühendus katkestatud" #: src/service/ui/settings.js:336 msgid "Connected" msgstr "Ühendatud" #. TRANSLATORS: Description of where directly shared files are stored. #: src/service/ui/settings.js:406 #, javascript-format msgid "Transferred files are placed in the Downloads folder." msgstr "Edastatud failid asetatakse Allalaadimiste kausta." #: src/service/ui/settings.js:499 msgid "A complete KDE Connect implementation for GNOME" msgstr "Täielik KDE Connect'i teostus GNOMEle" #. TRANSLATORS: eg. 'Translator Name ' #: src/service/ui/settings.js:508 msgid "translator-credits" msgstr "Madis O, 2018." #: src/service/ui/settings.js:537 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/service/ui/settings.js:540 msgid "Review Log" msgstr "Vaata logi üle" #. TRANSLATORS: When the battery level is 100% #: src/shell/device.js:82 msgid "Fully Charged" msgstr "Täielikult laetud" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:86 #, 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:96 #, 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:104 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (jäänud %d∶%02d)" #: src/shell/donotdisturb.js:150 src/shell/donotdisturb.js:289 msgid "Do Not Disturb" msgstr "Mitte segada" #: src/shell/donotdisturb.js:156 msgid "Silence Mobile Device Notifications" msgstr "Vaigista mobiilseadme teated" #: src/shell/donotdisturb.js:171 msgid "Until you turn off Do Not Disturb" msgstr "Kuni lülitad välja režiimi Mitte segada" #. TRANSLATORS: Time until change with time duration #. EXAMPLE: Until 10:00 (2 hours) #: src/shell/donotdisturb.js:184 src/shell/donotdisturb.js:278 #, javascript-format msgid "Until %s (%s)" msgstr "Kuni %s (%s)" #: src/shell/donotdisturb.js:216 msgid "Done" msgstr "Valmis" #. TRANSLATORS: Time duration in hours (eg. 2 hours) #: src/shell/donotdisturb.js:263 #, javascript-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "%d tund" msgstr[1] "%d tundi" #: src/shell/notification.js:42 msgid "Reply" msgstr "Vasta" #. TRANSLATORS: Extension name #: webextension/gettext.js:27 msgid "GSConnect" msgstr "GSConnect" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:29 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:33 msgid "Service Unavailable" msgstr "Teenus pole saadaval" #. TRANSLATORS: No devices are known or available #: webextension/gettext.js:35 msgid "No Device Found" msgstr "Seadet ei leitud" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:37 msgid "Open in Browser" msgstr "Ava brauseris" gnome-shell-extension-gsconnect-20/po/fr.po000066400000000000000000000606071341554142200211010ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-12-29 16:44-0800\n" "PO-Revision-Date: 2019-01-01 11:42\n" "Last-Translator: andyholmes \n" "Language-Team: French\n" "Language: 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-Generator: crowdin.com\n" "X-Crowdin-Project: gsconnect\n" "X-Crowdin-Language: fr\n" "X-Crowdin-File: /master/po/org.gnome.Shell.Extensions.GSConnect.pot\n" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/connect.ui:24 data/menus.ui:7 msgid "Connect to…" msgstr "Se connecter à…" #. Action Buttons #: data/connect.ui:30 data/notification.ui:14 data/telephony.ui:14 #: src/service/plugins/share.js:139 src/service/plugins/share.js:310 #: src/service/plugins/share.js:456 src/service/ui/device.js:607 #: src/service/ui/keybindings.js:44 src/service/ui/service.js:37 #: src/service/ui/settings.js:539 src/shell/donotdisturb.js:215 msgid "Cancel" msgstr "Annuler" #: data/connect.ui:37 msgid "Connect" msgstr "Connecter" #: data/connect.ui:98 msgid "IP Address" msgstr "Adresse IP" #: data/connect.ui:142 msgid "Bluetooth Device" msgstr "Périphérique bluetooth" #: data/contacts.ui:49 msgid "Type a phone number or name" msgstr "Taper un numéro de téléphone ou un nom" #: data/contacts.ui:83 msgid "No contacts" msgstr "\"Aucun contact\"" #: data/contacts.ui:95 data/menus.ui:33 data/messaging.ui:247 msgid "Help" msgstr "Aide" #: data/conversation.ui:76 data/conversation.ui:85 src/shell/notification.js:51 msgid "Type a message" msgstr "Taper un message" #: data/conversation.ui:84 msgid "Send Message" msgstr "Envoyer le message" #: data/device.ui:67 src/service/plugins/battery.js:12 msgid "Battery" msgstr "Batterie" #: data/device.ui:122 msgid "Clipboard Sync" msgstr "Synchroniser le presse-papiers" #: data/device.ui:185 msgid "Media Players" msgstr "Lecteurs multimédia" #: data/device.ui:240 msgid "Mouse & Keyboard" msgstr "Souris et clavier" #: data/device.ui:295 msgid "Volume Control" msgstr "Gestion du volume" #: data/device.ui:345 data/device.ui:1728 msgid "Sharing" msgstr "Partage" #: data/device.ui:374 data/device.ui:651 data/device.ui:1774 #: src/service/plugins/runcommand.js:18 src/service/plugins/runcommand.js:169 msgid "Commands" msgstr "Commandes" #: data/device.ui:424 data/device.ui:427 msgid "Name" msgstr "Nom" #: data/device.ui:440 data/device.ui:446 msgid "Command Line" msgstr "Commande" #: data/device.ui:444 data/device.ui:445 msgid "Choose an executable" msgstr "Choisir un exécutable" #: data/device.ui:496 data/device.ui:511 msgid "Add" msgstr "Ajouter" #: data/device.ui:527 data/device.ui:542 msgid "Remove" msgstr "Supprimer" #: data/device.ui:576 data/device.ui:588 msgid "Edit" msgstr "Modifier" #: data/device.ui:604 data/device.ui:616 msgid "Save" msgstr "Enregistrer" #: data/device.ui:712 msgid "Share Notifications" msgstr "Synchroniser les notifications" #: data/device.ui:763 msgid "Applications" msgstr "Applications" #: data/device.ui:809 data/device.ui:1820 #: src/service/plugins/notification.js:13 msgid "Notifications" msgstr "Notifications" #: data/device.ui:839 msgid "Incoming Calls" msgstr "Appels entrants" #: data/device.ui:888 data/device.ui:1055 msgid "Volume" msgstr "Volume" #: data/device.ui:954 data/device.ui:1120 msgid "Pause Media" msgstr "Mettre les médias en pause" #: data/device.ui:1007 msgid "Ongoing Calls" msgstr "Appels en cours" #: data/device.ui:1176 msgid "Mute Microphone" msgstr "Mettre le microphone en sourdine" #: data/device.ui:1230 data/device.ui:1866 src/service/plugins/telephony.js:13 msgid "Telephony" msgstr "Téléphonie" #: data/device.ui:1265 msgid "Action Shortcuts" msgstr "Raccourcis d'actions" #: data/device.ui:1280 data/device.ui:1349 msgid "Reset All…" msgstr "Tout réinitialiser…" #: data/device.ui:1334 msgid "Command Shortcuts" msgstr "Raccourcis de commandes" #: data/device.ui:1398 msgid "Shortcuts" msgstr "Raccourcis" #: data/device.ui:1429 msgid "Plugins" msgstr "Greffons" #: data/device.ui:1475 msgid "Experimental" msgstr "Expérimental" #: data/device.ui:1524 msgid "Legacy SMS Support" msgstr "Ancienne gestion des SMS" #: data/device.ui:1598 msgid "Delete" msgstr "Effacer" #: data/device.ui:1627 msgid "Delete this device" msgstr "Supprimer cet appareil" #: data/device.ui:1645 msgid "Unpair and remove all settings and files" msgstr "Dissocier et supprimer tous les paramètres et fichiers" #: data/device.ui:1678 data/device.ui:1958 msgid "Advanced" msgstr "Avancé" #: data/device.ui:1912 msgid "Keyboard Shortcuts" msgstr "Raccourcis clavier" #. TRANSLATORS: Send a pair request to the device #: data/device.ui:2015 data/menus.ui:68 src/service/device.js:424 msgid "Pair" msgstr "Associer" #: data/device.ui:2047 msgid "Device is unpaired" msgstr "L'appareil n'est pas pairé" #: data/device.ui:2062 msgid "You may configure this device before pairing" msgstr "Vous pouvez configurer cet appareil avant le pairage" #: data/menus.ui:12 msgid "Display Mode" msgstr "Mode d'affichage" #. TRANSLATORS: Show device indicators in the top bar #: data/menus.ui:15 msgid "Panel" msgstr "Barre des tâches" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/menus.ui:21 msgid "User Menu" msgstr "Menu utilisateur" #. TRANSLATORS: Generate a support log #: data/menus.ui:29 src/service/ui/settings.js:536 msgid "Generate Support Log" msgstr "Générer des logs pour le support" #: data/menus.ui:38 msgid "About" msgstr "À propos" #. TRANSLATORS: Change the connection type to Bluetooth #: data/menus.ui:49 msgid "Switch to Bluetooth" msgstr "Se connecter en Bluetooth" #. TRANSLATORS: Change the connection type to TCP/IP #: data/menus.ui:56 msgid "Switch to LAN" msgstr "Se connecter au réseau local" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/menus.ui:63 src/service/ui/device.js:317 msgid "Encryption Info" msgstr "Informations de chiffrement" #. TRANSLATORS: Unpair the device and notify it #: data/menus.ui:74 src/service/device.js:432 msgid "Unpair" msgstr "Dissocier" #. TRANSLATORS: Send clipboard content to device #: data/menus.ui:86 msgid "To Device" msgstr "Vers l'Appareil" #. TRANSLATORS: Receive clipboard content from the device #: data/menus.ui:92 msgid "From Device" msgstr "Depuis l'Appareil" #. TRANSLATORS: Don't change the system volume #: data/menus.ui:104 data/menus.ui:130 msgid "Nothing" msgstr "Ne rien faire" #. TRANSLATORS: Lower the system volume #: data/menus.ui:111 data/menus.ui:137 msgid "Lower" msgstr "Réduire" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the phone ringer #: data/menus.ui:118 data/menus.ui:144 src/service/plugins/telephony.js:194 msgid "Mute" msgstr "Mettre en sourdine" #: data/messaging.ui:12 src/service/plugins/sms.js:26 #: src/service/ui/messaging.js:911 msgid "Messaging" msgstr "Messagerie" #: data/messaging.ui:21 src/service/ui/messaging.js:992 msgid "New Conversation" msgstr "Nouvelle conversation" #: data/messaging.ui:110 msgid "No conversation selected" msgstr "Aucune conversation sélectionnée" #: data/messaging.ui:126 msgid "Select or start a conversation" msgstr "Choisir ou commencer une discussion" #: data/messaging.ui:193 data/notification.ui:52 data/telephony.ui:52 msgid "Device is disconnected" msgstr "L'appareil est déconnecté" #: data/messaging.ui:264 msgid "No Conversations" msgstr "Aucune discussion" #: data/notification.ui:21 data/telephony.ui:21 #: src/service/plugins/share.js:457 msgid "Send" msgstr "Envoyer" #: data/settings.ui:10 msgid "Searching for devices…" msgstr "Recherche d'appareils…" #: data/settings.ui:49 data/settings.ui:63 msgid "Refresh" msgstr "Rafraîchir" #: data/settings.ui:74 data/settings.ui:91 src/extension.js:105 msgid "Mobile Settings" msgstr "Paramètres" #: data/settings.ui:144 data/settings.ui:158 msgid "Edit Device Name" msgstr "Modifier le nom de l'appareil" #: data/settings.ui:236 msgid "Devices" msgstr "Appareils" #: data/settings.ui:299 msgid "Browser Add-Ons" msgstr "Extensions du navigateur" #: data/settings.ui:579 msgid "Enable" msgstr "Activer" #: data/settings.ui:611 msgid "This device is invisible to unpaired devices" msgstr "Cet appareil est invisible par les appareils non pairés" #: data/settings.ui:623 src/service/daemon.js:532 msgid "Discovery Disabled" msgstr "Découverte désactivée" #. TRANSLATORS: Share URL by SMS #: data/telephony.ui:9 src/service/daemon.js:718 src/service/plugins/sms.js:50 #: webextension/gettext.js:39 msgid "Send SMS" msgstr "Envoyer un SMS" #. Service Menu #: src/extension.js:79 src/extension.js:255 msgid "Mobile Devices" msgstr "Appareils mobiles" #. TRANSLATORS: %d is the number of devices connected #: src/extension.js:253 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d Connecté" msgstr[1] "%d Connectés" #. TRANSLATORS: Top-level context menu item for GSConnect #: src/nautilus-gsconnect.py:112 webextension/gettext.js:31 msgid "Send To Mobile Device" msgstr "Envoyer vers l'appareil mobile" #: src/service/daemon.js:372 msgid "Report" msgstr "Signaler" #: src/service/daemon.js:507 msgid "Authentication Failure" msgstr "Échec d'authentification" #: src/service/daemon.js:516 msgid "Network Error" msgstr "Erreur réseau" #: src/service/daemon.js:517 src/service/daemon.js:525 msgid "Click for help troubleshooting" msgstr "Cliquer pour l'aide de dépannage" #: src/service/daemon.js:524 msgid "PulseAudio Error" msgstr "Erreur PulseAudio" #: src/service/daemon.js:533 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/daemon.js:541 #, javascript-format msgid "%s Plugin Failed To Load" msgstr "Échec du chargement du greffon %s" #: src/service/daemon.js:542 msgid "Click for more information" msgstr "Cliquer pour plus d'informations" #: src/service/daemon.js:724 msgid "Dial Number" msgstr "Composer le numéro" #: src/service/daemon.js:730 src/service/plugins/share.js:27 msgid "Share File" msgstr "Partager un fichier" #: src/service/device.js:161 msgid "Not available" msgstr "Indisponible" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:165 #, javascript-format msgid "Bluetooth device at %s" msgstr "Périphérique Bluetooth à %s" #. TRANSLATORS: Label for TLS Certificate fingerprint #. #. Example: #. #. Google Pixel Fingerprint: #. 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 #: src/service/device.js:183 src/service/device.js:185 #, javascript-format msgid "%s Fingerprint:" msgstr "Empreinte %s:" #: src/service/device.js:242 msgid "Laptop" msgstr "Ordinateur portable" #: src/service/device.js:244 msgid "Smartphone" msgstr "Smartphone" #: src/service/device.js:246 msgid "Tablet" msgstr "Tablette" #: src/service/device.js:248 msgid "Desktop" msgstr "Ordinateur de bureau" #: src/service/device.js:408 src/service/ui/device.js:15 msgid "Reconnect" msgstr "Reconnecter" #: src/service/device.js:416 src/service/ui/device.js:16 #: src/service/ui/settings.js:371 msgid "Settings" msgstr "Paramètres" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:607 #, javascript-format msgid "Pair Request from %s" msgstr "Demande d'association depuis %s" #: src/service/device.js:614 msgid "Reject" msgstr "Rejeter" #: src/service/device.js:619 msgid "Accept" msgstr "Accepter" #: src/service/plugins/battery.js:188 src/service/plugins/findmyphone.js:19 msgid "Ring" msgstr "Faire sonner" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:197 #, javascript-format msgid "%s: Battery is low" msgstr "%s: la batterie est faible" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:199 #, javascript-format msgid "%d%% remaining" msgstr "%d%% restant" #: src/service/plugins/clipboard.js:11 msgid "Clipboard" msgstr "Presse-papiers" #: src/service/plugins/clipboard.js:17 msgid "Clipboard Push" msgstr "Envoyer le presse-papiers" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Pull" msgstr "Récupérer le presse-papiers" #: src/service/plugins/contacts.js:12 msgid "Contacts" msgstr "Contacts" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #: src/service/plugins/contacts.js:263 src/service/plugins/telephony.js:173 #: src/service/plugins/telephony.js:224 src/service/plugins/telephony.js:264 #: src/service/ui/contacts.js:156 src/service/ui/contacts.js:397 msgid "Unknown Contact" msgstr "Contact inconnu" #: src/service/plugins/findmyphone.js:13 msgid "Find My Phone" msgstr "Trouver mon téléphone" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Pavé tactile" #: src/service/plugins/mousepad.js:28 src/service/plugins/mousepad.js:569 msgid "Keyboard" msgstr "Clavier" #: src/service/plugins/mousepad.js:223 msgid "Additional Software Required" msgstr "Logiciels supplémentaires requis" #: src/service/plugins/mousepad.js:586 msgid "Keyboard not ready" msgstr "Le clavier n'est pas prêt" #: src/service/plugins/mpris.js:10 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/notification.js:26 msgid "Cancel Notification" msgstr "Annuler la notification" #: src/service/plugins/notification.js:34 msgid "Close Notification" msgstr "Fermer la notification" #: src/service/plugins/notification.js:42 msgid "Reply Notification" msgstr "Répondre à la notification" #: src/service/plugins/notification.js:50 msgid "Send Notification" msgstr "Envoyer la notification" #: src/service/plugins/ping.js:11 src/service/plugins/ping.js:17 #: src/service/plugins/ping.js:46 msgid "Ping" 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:53 #, javascript-format msgid "Ping: %s" msgstr "Ping : %s" #: src/service/plugins/runcommand.js:12 msgid "Run Commands" msgstr "Exécuter des commandes" #: src/service/plugins/sftp.js:12 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:18 msgid "Mount" msgstr "Monter" #: src/service/plugins/sftp.js:26 msgid "Unmount" msgstr "Démonter" #: src/service/plugins/sftp.js:159 msgid "All files" msgstr "Tous les fichiers" #: src/service/plugins/sftp.js:160 msgid "Camera pictures" msgstr "Images de la caméra" #: src/service/plugins/sftp.js:417 msgid "Files" msgstr "Fichiers" #: src/service/plugins/share.js:13 src/service/plugins/share.js:19 msgid "Share" msgstr "Partage" #: src/service/plugins/share.js:35 msgid "Share Text" msgstr "Partager du texte" #: src/service/plugins/share.js:43 src/service/ui/messaging.js:975 #: src/service/ui/messaging.js:983 msgid "Share Link" msgstr "Partager un lien" #: src/service/plugins/share.js:132 src/service/plugins/share.js:303 msgid "Starting Transfer" msgstr "Début du transfert" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:134 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Réception de « %s » depuis %s" #: src/service/plugins/share.js:172 src/service/plugins/share.js:327 msgid "Transfer Successful" msgstr "Transfert réussi" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:174 #, javascript-format msgid "Received “%s” from %s" msgstr "Reçu « %s » depuis %s" #: src/service/plugins/share.js:180 msgid "Open Folder" msgstr "Ouvrir le dossier" #: src/service/plugins/share.js:185 msgid "Open File" msgstr "Ouvrir le fichier" #: src/service/plugins/share.js:192 src/service/plugins/share.js:335 msgid "Transfer Failed" msgstr "Échec du transfert" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:194 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Échec de réception de « %s » depuis %s" #: src/service/plugins/share.js:233 #, 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:305 #, 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:329 #, 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:337 #, 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:408 #, 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:412 msgid "Open when done" msgstr "Ouvrir une fois fini" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:451 #, javascript-format msgid "Send a link to %s" msgstr "Envoyer un lien vers %s" #: src/service/plugins/sms.js:13 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:34 msgid "New SMS (URI)" msgstr "Nouveau SMS (URI)" #: src/service/plugins/sms.js:42 src/service/plugins/telephony.js:22 msgid "Reply SMS" msgstr "Répondre au SMS" #: src/service/plugins/sms.js:58 msgid "Share SMS" msgstr "Partager le SMS" #: src/service/plugins/systemvolume.js:11 msgid "System Volume" msgstr "Volume du système" #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Mettre l'appel en sourdine" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:190 msgid "Incoming call" msgstr "Appel entrant" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:205 msgid "Ongoing call" msgstr "Appel sortant" #. TRANSLATORS: All other phone number types #: src/service/ui/contacts.js:118 src/service/ui/contacts.js:139 #, javascript-format msgid "%s・Other" msgstr "%s・Autre" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:123 #, javascript-format msgid "%s・Fax" msgstr "%s・Fax" #. TRANSLATORS: A work phone number #: src/service/ui/contacts.js:127 #, javascript-format msgid "%s・Work" msgstr "%s・Travail" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:131 #, javascript-format msgid "%s・Mobile" msgstr "%s・Mobile" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:135 #, javascript-format msgid "%s・Home" msgstr "%s・Maison" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #: src/service/ui/contacts.js:278 src/service/ui/contacts.js:292 #, javascript-format msgid "Send to %s" msgstr "Envoyer vers %s" #: src/service/ui/device.js:608 msgid "Open" msgstr "Ouvrir" #: src/service/ui/device.js:660 src/service/ui/device.js:673 msgid "On" msgstr "Activé" #: src/service/ui/device.js:660 src/service/ui/device.js:673 msgid "Off" msgstr "Désactivé" #: src/service/ui/device.js:798 src/service/ui/device.js:826 #: src/service/ui/device.js:850 msgid "Disabled" msgstr "Désactivé" #. TRANSLATORS: Title of keyboard shortcut dialog #: src/service/ui/keybindings.js:33 msgid "Set Shortcut" msgstr "Définir un raccourci" #. TRANSLATORS: Button to confirm the new shortcut #: src/service/ui/keybindings.js:47 msgid "Set" msgstr "Définir" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/service/ui/keybindings.js:54 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Entrer un nouveau raccourci pour modifier %s" #. TRANSLATORS: Keys for cancelling (␛) or resetting (␈) a shortcut #: src/service/ui/keybindings.js:83 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." #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/service/ui/keybindings.js:182 #, javascript-format msgid "%s is already being used" msgstr "%s est déjà utilisé" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:65 src/service/ui/messaging.js:98 msgid "Just now" msgstr "À l'instant" #. TRANSLATORS: Time duration in minutes (eg. 15 minutes) #: src/service/ui/messaging.js:70 src/service/ui/messaging.js:102 #: src/shell/donotdisturb.js:266 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minute" msgstr[1] "%d minutes" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:75 #, javascript-format msgid "Yesterday・%s" msgstr "Hier・%s" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:243 #, javascript-format msgid "You: %s" msgstr "Vous: %s" #: src/service/ui/service.js:31 msgid "Select a Device" msgstr "Sélectionner un appareil" #: src/service/ui/service.js:35 msgid "Select" msgstr "Sélectionner" #: src/service/ui/settings.js:331 msgid "Unpaired" msgstr "Dissocié" #: src/service/ui/settings.js:333 msgid "Disconnected" msgstr "Déconnecté" #: src/service/ui/settings.js:336 msgid "Connected" msgstr "Connecté" #. TRANSLATORS: Description of where directly shared files are stored. #: src/service/ui/settings.js:406 #, javascript-format msgid "Transferred files are placed in the Downloads folder." msgstr "Les fichiers téléchargés sont enregistrés dans le dossier Downloads ." #: src/service/ui/settings.js:499 msgid "A complete KDE Connect implementation for GNOME" msgstr "Une implémentation complète de KDE Connect pour GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/service/ui/settings.js:508 msgid "translator-credits" msgstr "Mickaël Coiraton " #: src/service/ui/settings.js:537 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/service/ui/settings.js:540 msgid "Review Log" msgstr "Vérifier les logs" #. TRANSLATORS: When the battery level is 100% #: src/shell/device.js:82 msgid "Fully Charged" msgstr "Complètement chargé" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:86 #, 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:96 #, 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:104 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d restant)" #: src/shell/donotdisturb.js:150 src/shell/donotdisturb.js:289 msgid "Do Not Disturb" msgstr "Ne pas déranger" #: src/shell/donotdisturb.js:156 msgid "Silence Mobile Device Notifications" msgstr "Désactiver les notifications de l'appareil mobile" #: src/shell/donotdisturb.js:171 msgid "Until you turn off Do Not Disturb" msgstr "Jusqu'à ce que vous désactiviez Ne pas déranger" #. TRANSLATORS: Time until change with time duration #. EXAMPLE: Until 10:00 (2 hours) #: src/shell/donotdisturb.js:184 src/shell/donotdisturb.js:278 #, javascript-format msgid "Until %s (%s)" msgstr "Jusqu'à %s (%s)" #: src/shell/donotdisturb.js:216 msgid "Done" msgstr "Terminé" #. TRANSLATORS: Time duration in hours (eg. 2 hours) #: src/shell/donotdisturb.js:263 #, javascript-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "%d heure(s)" msgstr[1] "" #: src/shell/notification.js:42 msgid "Reply" msgstr "Répondre" #. TRANSLATORS: Extension name #: webextension/gettext.js:27 msgid "GSConnect" msgstr "GSConnect" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:29 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:33 msgid "Service Unavailable" msgstr "Service indisponible" #. TRANSLATORS: No devices are known or available #: webextension/gettext.js:35 msgid "No Device Found" msgstr "Aucun appareil trouvé" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:37 msgid "Open in Browser" msgstr "Ouvrir dans le navigateur" gnome-shell-extension-gsconnect-20/po/hu.po000066400000000000000000000557731341554142200211160ustar00rootroot00000000000000# Hungarian translation of GSConnect. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the org.gnome.Shell.Extensions.GSConnect package. # Báthory Péter , 2018. # msgid "" msgstr "" "Project-Id-Version: org.gnome.Shell.Extensions.GSConnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-11-19 11:14-0800\n" "PO-Revision-Date: 2018-11-26 23:27+0100\n" "Last-Translator: Báthory Péter \n" "Language-Team: \n" "Language: 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-Generator: Poedit 2.1.1\n" #: data/conversation.ui:71 data/conversation.ui:79 msgid "Type a message" msgstr "Írja be az üzenetet" #: data/contacts.ui:51 data/contacts.ui:52 msgid "Type a phone number or name" msgstr "Adjon meg egy számot vagy nevet" #: data/device.ui:68 src/service/plugins/battery.js:12 msgid "Battery" msgstr "Akkumulátor" #: data/device.ui:116 msgid "Clipboard Sync" msgstr "Vágólap szinkronizálás" #: data/device.ui:172 msgid "Media Players" msgstr "Médialejátszók" #: data/device.ui:220 msgid "Mouse & Keyboard" msgstr "Egér és billentyűzet" #: data/device.ui:268 msgid "Volume Control" msgstr "Hangerőszabályzó" #: data/device.ui:308 data/device.ui:1406 msgid "Sharing" msgstr "Megosztás" #: data/device.ui:337 data/device.ui:534 data/device.ui:1449 #: src/service/plugins/runcommand.js:18 src/service/plugins/runcommand.js:169 msgid "Commands" msgstr "Parancsok" #: data/device.ui:385 msgid "Name" msgstr "Név" #: data/device.ui:401 data/device.ui:402 msgid "Choose an executable" msgstr "Válassz egy programot" #: data/device.ui:403 msgid "Command Line" msgstr "Parancssor" #: data/device.ui:597 msgid "Share Notifications" msgstr "Értesítések megosztása" #: data/device.ui:636 msgid "Applications" msgstr "Alkalmazások" #: data/device.ui:675 data/device.ui:1492 #: src/service/plugins/notification.js:12 msgid "Notifications" msgstr "Értesítések" #: data/device.ui:705 msgid "Incoming Calls" msgstr "Bejövő hívások" #: data/device.ui:755 data/device.ui:900 msgid "Volume" msgstr "Hangerő" #: data/device.ui:813 data/device.ui:958 msgid "Pause Media" msgstr "Média szüneteltetése" #: data/device.ui:852 msgid "Ongoing Calls" msgstr "Kimenő hívások" #: data/device.ui:1007 msgid "Mute Microphone" msgstr "Mikrofon némítása" #: data/device.ui:1047 data/device.ui:1535 src/service/plugins/telephony.js:12 msgid "Telephony" msgstr "Telefonálás" #: data/device.ui:1082 msgid "Action Shortcuts" msgstr "Művelet gyorsbillentyűk" #: data/device.ui:1094 data/device.ui:1157 msgid "Reset All…" msgstr "Összes visszaállítása…" #: data/device.ui:1145 msgid "Command Shortcuts" msgstr "Parancs gyorsbillentyűk" #: data/device.ui:1203 msgid "Shortcuts" msgstr "Gyorsbillentyűk" #: data/device.ui:1234 msgid "Plugins" msgstr "Bővítmények" #: data/device.ui:1295 msgid "Delete" msgstr "Törlés" #: data/device.ui:1320 msgid "Delete this device" msgstr "Eszköz törlése" #: data/device.ui:1335 msgid "Unpair and remove all settings and files" msgstr "Párosítás megszüntetése és az összes beállítás és fájl törlése" #: data/device.ui:1365 data/device.ui:1621 msgid "Advanced" msgstr "Haladó" #: data/device.ui:1578 msgid "Keyboard Shortcuts" msgstr "Gyorsbillentyűk" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/menus.ui:7 src/service/ui/service.js:115 msgid "Connect to…" msgstr "Kapcsolódás egy eszközhöz…" #: data/menus.ui:13 data/settings.ui:881 msgid "Help" msgstr "Súgó" #. TRANSLATORS: Open the developer's dialog #: data/menus.ui:19 msgid "Debugger" msgstr "Hibakereső" #: data/menus.ui:23 msgid "About" msgstr "Névjegy" #. TRANSLATORS: Change the connection type to Bluetooth #: data/menus.ui:34 msgid "Switch to Bluetooth" msgstr "Váltás Bluetoothra" #. TRANSLATORS: Change the connection type to TCP/IP #: data/menus.ui:41 msgid "Switch to LAN" msgstr "Váltás LAN-ra" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/menus.ui:48 src/service/ui/settings.js:612 msgid "Encryption Info" msgstr "Titkosítási információ" #. TRANSLATORS: Send a pair request to the device #: data/menus.ui:53 src/service/device.js:425 msgid "Pair" msgstr "Párosítás" #. TRANSLATORS: Unpair the device and notify it #: data/menus.ui:59 src/service/device.js:433 msgid "Unpair" msgstr "Párosítás megszűntetése" #. TRANSLATORS: Send clipboard content to device #: data/menus.ui:71 msgid "To Device" msgstr "Eszközre" #. TRANSLATORS: Receive clipboard content from the device #: data/menus.ui:77 msgid "From Device" msgstr "Eszközről" #. TRANSLATORS: Don't change the system volume #: data/menus.ui:89 data/menus.ui:115 msgid "Nothing" msgstr "Semmi" #. TRANSLATORS: Lower the system volume #: data/menus.ui:96 data/menus.ui:122 msgid "Lower" msgstr "Halkítás" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the phone ringer #: data/menus.ui:103 data/menus.ui:129 src/service/plugins/telephony.js:176 msgid "Mute" msgstr "Némítás" #: data/messaging.ui:12 src/service/plugins/sms.js:25 #: src/service/ui/messaging.js:871 msgid "Messaging" msgstr "Üzenetküldés" #: data/messaging.ui:110 msgid "Select a conversation" msgstr "Beszélgetés kiválasztása" #: data/messaging.ui:188 msgid "Device is disconnected" msgstr "Az eszköz nincs csatlakoztatva" #: data/settings.ui:333 msgid "Appearance" msgstr "Megjelenítés" #: data/settings.ui:383 msgid "Display Mode" msgstr "Megjelenítési mód" #: data/settings.ui:425 msgid "Service" msgstr "Szolgáltatás" #: data/settings.ui:475 msgid "Discoverable" msgstr "Felderíthető" #: data/settings.ui:528 msgid "Restart Service" msgstr "Szolgáltatás újraindítása" #: data/settings.ui:581 data/settings.ui:1015 src/service/device.js:417 msgid "Settings" msgstr "Beállítások" #: data/settings.ui:639 msgid "Remote Filesystems" msgstr "Távoli fájlrendszerek" #: data/settings.ui:675 msgid "Sound Effects" msgstr "Hanghatások" #: data/settings.ui:712 msgid "Extended Keyboard Support" msgstr "Kibővített billentyűzet támogatás" #: data/settings.ui:749 msgid "Desktop Contacts" msgstr "Asztali névjegyek" #: data/settings.ui:786 msgid "Files Integration" msgstr "Fájlkezelő integráció" #: data/settings.ui:813 msgid "Additional Features" msgstr "További funkciók" #: data/settings.ui:903 msgid "Browser Add-Ons" msgstr "Böngésző bővítmények" #: data/settings.ui:921 msgid "KDE Connect" msgstr "KDE Connect" #: data/settings.ui:956 data/settings.ui:1058 msgid "Other" msgstr "Egyéb" #. TRANSLATORS: No devices are known or available #: data/settings.ui:1106 webextension/gettext.js:35 msgid "No Device Found" msgstr "Nem található eszköz" #: data/settings.ui:1142 msgid "Refresh" msgstr "Frissítés" #. Service Menu #: src/extension.js:79 src/extension.js:259 msgid "Mobile Devices" msgstr "Mobil eszközök" #: src/extension.js:105 msgid "Mobile Settings" msgstr "Mobil beállítások" #. TRANSLATORS: Extension name #: src/extension.js:196 src/extension.js:311 src/service/daemon.js:95 #: src/service/daemon.js:413 webextension/gettext.js:27 msgid "GSConnect" msgstr "GSConnect" #. TRANSLATORS: %d is the number of devices connected #: src/extension.js:257 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "Egy eszköz csatlakoztatva" msgstr[1] "%d eszköz csatlakoztatva" #. TRANSLATORS: Top-level context menu item for GSConnect #: src/nautilus-gsconnect.py:112 webextension/gettext.js:31 msgid "Send To Mobile Device" msgstr "Küldés mobil eszközre" #: src/service/daemon.js:406 msgid "A complete KDE Connect implementation for GNOME" msgstr "Teljes KDE Connect implementáció GNOME-hoz" #. TRANSLATORS: eg. 'Translator Name ' #: src/service/daemon.js:415 msgid "translator-credits" msgstr "Báthory Péter " #: src/service/daemon.js:437 msgid "Report" msgstr "Jelentés" #: src/service/daemon.js:567 msgid "Authentication Failure" msgstr "Hitelesítési hiba" #: src/service/daemon.js:576 msgid "Network Error" msgstr "Hálózati hiba" #: src/service/daemon.js:577 src/service/daemon.js:587 msgid "Click for help troubleshooting" msgstr "Kattintson ide, hogy segítséget kapj a hibaelhárításhoz" #: src/service/daemon.js:586 msgid "PulseAudio Error" msgstr "PulseAudio hiba" #: src/service/daemon.js:596 msgid "Discovery Disabled" msgstr "Felderítés letiltva" #: src/service/daemon.js:597 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/daemon.js:599 src/service/daemon.js:609 msgid "Click to open preferences" msgstr "Kattintson ide a beállítások megnyitásához" #: src/service/daemon.js:608 msgid "Additional Software Required" msgstr "További szoftverre van szükség" #: src/service/daemon.js:617 #, javascript-format msgid "%s Plugin Failed To Load" msgstr "%s bővítményt nem sikerült betölteni" #: src/service/daemon.js:618 src/service/daemon.js:635 msgid "Click for more information" msgstr "További információért kattintson ide" #: src/service/daemon.js:633 msgid "Wayland Not Supported" msgstr "Wayland nem támogatott" #: src/service/daemon.js:634 msgid "Remote input not supported on Wayland" msgstr "A távoli bevitel nem támogatott Wayland alatt" #. Create an urgent notification #: src/service/daemon.js:658 #, javascript-format msgid "GSConnect: %s" msgstr "GSConnect: %s" #. TRANSLATORS: Share URL by SMS #: src/service/daemon.js:808 src/service/plugins/sms.js:49 #: webextension/gettext.js:39 msgid "Send SMS" msgstr "SMS küldése" #: src/service/daemon.js:812 msgid "Dial Number" msgstr "Telefonszám hívása" #: src/service/device.js:166 msgid "Not available" msgstr "Nem érhető el" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:170 #, javascript-format msgid "Bluetooth device at %s" msgstr "Bluetooth eszköz itt: %s" #. TRANSLATORS: Label for TLS Certificate fingerprint #. #. Example: #. #. Google Pixel Fingerprint: #. 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 #: src/service/device.js:188 src/service/device.js:190 #, javascript-format msgid "%s Fingerprint:" msgstr "%s ujjlenyomat:" #: src/service/device.js:240 msgid "Laptop" msgstr "Laptop" #: src/service/device.js:242 msgid "Smartphone" msgstr "Okostelefon" #: src/service/device.js:244 msgid "Tablet" msgstr "Táblagép" #: src/service/device.js:246 msgid "Desktop" msgstr "Asztali gép" #: src/service/device.js:409 msgid "Reconnect" msgstr "Újracsatlakozás" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:606 #, javascript-format msgid "Pair Request from %s" msgstr "Párosítási kérelem innen: %s" #: src/service/device.js:613 msgid "Reject" msgstr "Elutasítás" #: src/service/device.js:618 msgid "Accept" msgstr "Elfogadás" #: src/service/plugins/battery.js:194 src/service/plugins/findmyphone.js:18 #, fuzzy msgid "Ring" msgstr "Keresés" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:203 #, javascript-format msgid "%s: Battery is low" msgstr "%s: alacsony töltöttség" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:205 #, javascript-format msgid "%d%% remaining" msgstr "%d%% van hátra" #: src/service/plugins/clipboard.js:11 msgid "Clipboard" msgstr "Vágólap" #: src/service/plugins/clipboard.js:17 msgid "Clipboard Push" msgstr "Vágólap küldés" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Pull" msgstr "Vágólap fogadás" #: src/service/plugins/contacts.js:12 msgid "Contacts" msgstr "Névjegyek" #: src/service/plugins/findmyphone.js:12 msgid "Find My Phone" msgstr "Keresd meg a telefonom" #: src/service/plugins/findmyphone.js:65 msgid "Locate Device" msgstr "Eszköz keresése" #: src/service/plugins/findmyphone.js:66 #, javascript-format msgid "%s asked to locate this device" msgstr "%s kérte ennek az eszköznek a megkeresését" #: src/service/plugins/findmyphone.js:74 msgid "Found" msgstr "Megtalálva" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Egérpad" #: src/service/plugins/mousepad.js:28 src/service/plugins/mousepad.js:578 msgid "Keyboard" msgstr "Billentyűzet" #: src/service/plugins/mousepad.js:595 msgid "Keyboard not ready" msgstr "A billentyűzet nem áll készen" #: src/service/plugins/mpris.js:10 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/notification.js:25 msgid "Cancel Notification" msgstr "Értesítés megszakítása" #: src/service/plugins/notification.js:33 msgid "Close Notification" msgstr "Értesítés bezárása" #: src/service/plugins/notification.js:41 msgid "Reply Notification" msgstr "Értesítések küldése" #: src/service/plugins/notification.js:49 msgid "Send Notification" msgstr "Értesítés küldése" #: src/service/plugins/ping.js:11 src/service/plugins/ping.js:17 #: src/service/plugins/ping.js:46 msgid "Ping" 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:53 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/runcommand.js:12 msgid "Run Commands" msgstr "Parancsok futtatása" #: src/service/plugins/sftp.js:12 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:18 msgid "Mount" msgstr "Csatolás" #: src/service/plugins/sftp.js:26 msgid "Unmount" msgstr "Leválasztás" #: src/service/plugins/sftp.js:170 msgid "All files" msgstr "Összes fájl" #: src/service/plugins/sftp.js:171 msgid "Camera pictures" msgstr "Kamera fényképek" #: src/service/plugins/sftp.js:333 msgid "Files" msgstr "Fájlok" #: src/service/plugins/share.js:12 src/service/plugins/share.js:18 msgid "Share" msgstr "Megosztás" #: src/service/plugins/share.js:26 msgid "Share File" msgstr "Fájl megosztása" #: src/service/plugins/share.js:34 msgid "Share Text" msgstr "Szöveg megosztása" #: src/service/plugins/share.js:42 src/service/ui/messaging.js:903 #: src/service/ui/messaging.js:911 msgid "Share Link" msgstr "Hivatkozás megosztása" #: src/service/plugins/share.js:131 src/service/plugins/share.js:282 msgid "Starting Transfer" msgstr "Átvitel megkezdése" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:133 #, javascript-format msgid "Receiving “%s” from %s" msgstr "„%s” fogadása innen: %s" #. Action Buttons #: src/service/plugins/share.js:138 src/service/plugins/share.js:289 #: src/service/plugins/share.js:407 src/service/ui/keybindings.js:44 #: src/service/ui/service.js:121 src/service/ui/service.js:300 #: src/service/ui/settings.js:873 src/shell/donotdisturb.js:215 msgid "Cancel" msgstr "Mégse" #: src/service/plugins/share.js:149 src/service/plugins/share.js:303 msgid "Transfer Successful" msgstr "Sikeres átvitel" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:151 #, javascript-format msgid "Received “%s” from %s" msgstr "„%s” fogadva innen: %s" #: src/service/plugins/share.js:157 msgid "Open Folder" msgstr "Könyvtár megnyitása" #: src/service/plugins/share.js:162 msgid "Open File" msgstr "Fájl megnyitása" #: src/service/plugins/share.js:169 src/service/plugins/share.js:311 msgid "Transfer Failed" msgstr "Sikertelen átvitel" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:171 #, 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:211 #, 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:284 #, 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:305 #, 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:313 #, 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:384 #, javascript-format msgid "Send files to %s" msgstr "Fájlok küldése ide: %s" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:402 #, javascript-format msgid "Send a link to %s" msgstr "Hivatkozás küldése ide: %s" #: src/service/plugins/share.js:408 msgid "Send" msgstr "Küldés" #: src/service/plugins/sms.js:12 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:33 msgid "New SMS (URI)" msgstr "Új SMS (URI)" #: src/service/plugins/sms.js:41 msgid "Reply SMS" msgstr "SMS válasz" #: src/service/plugins/sms.js:57 msgid "Share SMS" msgstr "SMS megosztása" #: src/service/plugins/systemvolume.js:11 msgid "System Volume" msgstr "Rendszerhangerő" #: src/service/plugins/telephony.js:21 msgid "Mute Call" msgstr "Hívás némítása" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #: src/service/plugins/telephony.js:155 src/service/ui/contacts.js:96 #: src/service/ui/contacts.js:350 msgid "Unknown Contact" msgstr "Ismeretlen névjegy" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:172 msgid "Incoming call" msgstr "Bejövő hívás" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:187 msgid "Ongoing call" msgstr "Kimenő hívás" #. TRANSLATORS: All other phone number types #: src/service/ui/contacts.js:58 src/service/ui/contacts.js:79 #, javascript-format msgid "%s・Other" msgstr "%s・Egyéb" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:63 #, javascript-format msgid "%s・Fax" msgstr "%s・Fax" #. TRANSLATORS: A work phone number #: src/service/ui/contacts.js:67 #, javascript-format msgid "%s・Work" msgstr "%s・Munkahelyi" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:71 #, javascript-format msgid "%s・Mobile" msgstr "%s・Mobil" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:75 #, javascript-format msgid "%s・Home" msgstr "%s・Otthoni" #: src/service/ui/contacts.js:203 msgid "Select a contact or number" msgstr "Válassz egy névjegyet vagy telefonszámot" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #: src/service/ui/contacts.js:239 src/service/ui/contacts.js:253 #, javascript-format msgid "Send to %s" msgstr "Küldés neki: %s" #. TRANSLATORS: Title of keyboard shortcut dialog #: src/service/ui/keybindings.js:33 msgid "Set Shortcut" msgstr "Gyorsbillentyű beállítása" #. TRANSLATORS: Button to confirm the new shortcut #: src/service/ui/keybindings.js:47 msgid "Set" msgstr "Kiválasztás" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/service/ui/keybindings.js:54 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "%s megváltoztatásához adj meg egy új gyorsbillentyűt" #. TRANSLATORS: Keys for cancelling (␛) or resetting (␈) a shortcut #: src/service/ui/keybindings.js:83 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "" "A megszakításhoz nyomj Esc gombot, vagy Backspace-t a gyorsbillentyű " "visszaállításához." #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/service/ui/keybindings.js:182 #, javascript-format msgid "%s is already being used" msgstr "%s már használatban van" #: src/service/ui/service.js:122 msgid "Connect" msgstr "Csatlakozás" #: src/service/ui/service.js:294 msgid "Select a Device" msgstr "Eszköz kiválasztása" #: src/service/ui/service.js:298 msgid "Select" msgstr "Kiválasztás" #: src/service/ui/settings.js:298 msgid "Panel" msgstr "Panel" #: src/service/ui/settings.js:298 msgid "User Menu" msgstr "Felhasználói menü" #: src/service/ui/settings.js:874 msgid "Open" msgstr "Megnyitás" #: src/service/ui/settings.js:931 src/service/ui/settings.js:944 msgid "On" msgstr "Be" #: src/service/ui/settings.js:931 src/service/ui/settings.js:944 msgid "Off" msgstr "Ki" #: src/service/ui/settings.js:1065 src/service/ui/settings.js:1131 msgid "Disabled" msgstr "Letiltva" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:93 src/service/ui/messaging.js:126 msgid "Just now" msgstr "Épp most" #. TRANSLATORS: Time duration in minutes (eg. 15 minutes) #: src/service/ui/messaging.js:98 src/service/ui/messaging.js:130 #: src/shell/donotdisturb.js:266 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d perc" msgstr[1] "%d perc" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:103 #, javascript-format msgid "Yesterday・%s" msgstr "Tegnap・%s" #: src/service/ui/messaging.js:920 msgid "New Message" msgstr "Új üzenet" #. TRANSLATORS: When the battery level is 100% #: src/shell/device.js:86 msgid "Fully Charged" msgstr "Teljesen feltöltve" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:90 #, 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:100 #, 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:108 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d van hátra)" #: src/shell/donotdisturb.js:150 src/shell/donotdisturb.js:289 msgid "Do Not Disturb" msgstr "Ne zavarjanak" #: src/shell/donotdisturb.js:156 msgid "Silence Mobile Device Notifications" msgstr "Mobileszköz értesítéseinek némítása" #: src/shell/donotdisturb.js:171 msgid "Until you turn off Do Not Disturb" msgstr "Amíg ön ki nem kapcsolja a Ne zavarjanak funkciót" #. TRANSLATORS: Time until change with time duration #. EXAMPLE: Until 10:00 (2 hours) #: src/shell/donotdisturb.js:184 src/shell/donotdisturb.js:278 #, javascript-format msgid "Until %s (%s)" msgstr "%s-ig (%s)" #: src/shell/donotdisturb.js:216 msgid "Done" msgstr "Kész" #. TRANSLATORS: Time duration in hours (eg. 2 hours) #: src/shell/donotdisturb.js:263 #, javascript-format msgid "One hour" msgid_plural "%d hours" msgstr[0] "Egy óra" msgstr[1] "%d óra" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:29 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:33 msgid "Service Unavailable" msgstr "A szolgáltatás nem érhető el" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:37 msgid "Open in Browser" msgstr "Megnyitás böngészőben" gnome-shell-extension-gsconnect-20/po/it.po000066400000000000000000000577021341554142200211100ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-12-29 16:44-0800\n" "PO-Revision-Date: 2019-01-08 14:21\n" "Last-Translator: andyholmes \n" "Language-Team: Italian\n" "Language: 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-Generator: crowdin.com\n" "X-Crowdin-Project: gsconnect\n" "X-Crowdin-Language: it\n" "X-Crowdin-File: /master/po/org.gnome.Shell.Extensions.GSConnect.pot\n" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/connect.ui:24 data/menus.ui:7 msgid "Connect to…" msgstr "Connetti a…" #. Action Buttons #: data/connect.ui:30 data/notification.ui:14 data/telephony.ui:14 #: src/service/plugins/share.js:139 src/service/plugins/share.js:310 #: src/service/plugins/share.js:456 src/service/ui/device.js:607 #: src/service/ui/keybindings.js:44 src/service/ui/service.js:37 #: src/service/ui/settings.js:539 src/shell/donotdisturb.js:215 msgid "Cancel" msgstr "Annulla" #: data/connect.ui:37 msgid "Connect" msgstr "Connetti" #: data/connect.ui:98 msgid "IP Address" msgstr "Indirizzo IP" #: data/connect.ui:142 msgid "Bluetooth Device" msgstr "Dispositivo Bluetooth" #: data/contacts.ui:49 msgid "Type a phone number or name" msgstr "Digita un numero di telefono o un nome" #: data/contacts.ui:83 msgid "No contacts" msgstr "Nessun contatto" #: data/contacts.ui:95 data/menus.ui:33 data/messaging.ui:247 msgid "Help" msgstr "Aiuto" #: data/conversation.ui:76 data/conversation.ui:85 src/shell/notification.js:51 msgid "Type a message" msgstr "Digita un messaggio" #: data/conversation.ui:84 msgid "Send Message" msgstr "Invia un messaggio" #: data/device.ui:67 src/service/plugins/battery.js:12 msgid "Battery" msgstr "Batteria" #: data/device.ui:122 msgid "Clipboard Sync" msgstr "Sincronizzazione appunti" #: data/device.ui:185 msgid "Media Players" msgstr "Riproduzione multimediale" #: data/device.ui:240 msgid "Mouse & Keyboard" msgstr "Mouse e tastiera" #: data/device.ui:295 msgid "Volume Control" msgstr "Controllo volume" #: data/device.ui:345 data/device.ui:1728 msgid "Sharing" msgstr "Condivisione" #: data/device.ui:374 data/device.ui:651 data/device.ui:1774 #: src/service/plugins/runcommand.js:18 src/service/plugins/runcommand.js:169 msgid "Commands" msgstr "Comandi" #: data/device.ui:424 data/device.ui:427 msgid "Name" msgstr "Nome" #: data/device.ui:440 data/device.ui:446 msgid "Command Line" msgstr "Linea di comando" #: data/device.ui:444 data/device.ui:445 msgid "Choose an executable" msgstr "Seleziona un eseguibile" #: data/device.ui:496 data/device.ui:511 msgid "Add" msgstr "Aggiungi" #: data/device.ui:527 data/device.ui:542 msgid "Remove" msgstr "Rimuovi" #: data/device.ui:576 data/device.ui:588 msgid "Edit" msgstr "Modifica" #: data/device.ui:604 data/device.ui:616 msgid "Save" msgstr "Salva" #: data/device.ui:712 msgid "Share Notifications" msgstr "Condividi notifiche" #: data/device.ui:763 msgid "Applications" msgstr "Applicazioni" #: data/device.ui:809 data/device.ui:1820 #: src/service/plugins/notification.js:13 msgid "Notifications" msgstr "Notifiche" #: data/device.ui:839 msgid "Incoming Calls" msgstr "Chiamate in entrata" #: data/device.ui:888 data/device.ui:1055 msgid "Volume" msgstr "Volume" #: data/device.ui:954 data/device.ui:1120 msgid "Pause Media" msgstr "Metti in pausa il media" #: data/device.ui:1007 msgid "Ongoing Calls" msgstr "Chiamate in uscita" #: data/device.ui:1176 msgid "Mute Microphone" msgstr "Silenzia il microfono" #: data/device.ui:1230 data/device.ui:1866 src/service/plugins/telephony.js:13 msgid "Telephony" msgstr "Telefonia" #: data/device.ui:1265 msgid "Action Shortcuts" msgstr "Scorciatoie azioni" #: data/device.ui:1280 data/device.ui:1349 msgid "Reset All…" msgstr "Ripristina tutte…" #: data/device.ui:1334 msgid "Command Shortcuts" msgstr "Scorciatoie comandi" #: data/device.ui:1398 msgid "Shortcuts" msgstr "Scorciatoie" #: data/device.ui:1429 msgid "Plugins" msgstr "Plugin" #: data/device.ui:1475 msgid "Experimental" msgstr "Sperimentale" #: data/device.ui:1524 msgid "Legacy SMS Support" msgstr "Supporto SMS legacy" #: data/device.ui:1598 msgid "Delete" msgstr "Elimina" #: data/device.ui:1627 msgid "Delete this device" msgstr "Elimina questo dispositivo" #: data/device.ui:1645 msgid "Unpair and remove all settings and files" msgstr "Disaccoppia e rimuovi impostazioni e file" #: data/device.ui:1678 data/device.ui:1958 msgid "Advanced" msgstr "Avanzate" #: data/device.ui:1912 msgid "Keyboard Shortcuts" msgstr "Scorciatoie da tastiera" #. TRANSLATORS: Send a pair request to the device #: data/device.ui:2015 data/menus.ui:68 src/service/device.js:424 msgid "Pair" msgstr "Accoppia" #: data/device.ui:2047 msgid "Device is unpaired" msgstr "Il dispositivo è disaccoppiato" #: data/device.ui:2062 msgid "You may configure this device before pairing" msgstr "È possibile configurare questo dispositivo prima dell'accoppiamento" #: data/menus.ui:12 msgid "Display Mode" msgstr "Modalità di visualizzazione" #. TRANSLATORS: Show device indicators in the top bar #: data/menus.ui:15 msgid "Panel" msgstr "Pannello" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/menus.ui:21 msgid "User Menu" msgstr "Menu utente" #. TRANSLATORS: Generate a support log #: data/menus.ui:29 src/service/ui/settings.js:536 msgid "Generate Support Log" msgstr "Genera log di supporto" #: data/menus.ui:38 msgid "About" msgstr "Informazioni" #. TRANSLATORS: Change the connection type to Bluetooth #: data/menus.ui:49 msgid "Switch to Bluetooth" msgstr "Passa a Bluetooth" #. TRANSLATORS: Change the connection type to TCP/IP #: data/menus.ui:56 msgid "Switch to LAN" msgstr "Passa a LAN" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/menus.ui:63 src/service/ui/device.js:317 msgid "Encryption Info" msgstr "Informazioni di criptazione" #. TRANSLATORS: Unpair the device and notify it #: data/menus.ui:74 src/service/device.js:432 msgid "Unpair" msgstr "Disaccoppia" #. TRANSLATORS: Send clipboard content to device #: data/menus.ui:86 msgid "To Device" msgstr "Al dispositivo" #. TRANSLATORS: Receive clipboard content from the device #: data/menus.ui:92 msgid "From Device" msgstr "Dal dispositivo" #. TRANSLATORS: Don't change the system volume #: data/menus.ui:104 data/menus.ui:130 msgid "Nothing" msgstr "Niente" #. TRANSLATORS: Lower the system volume #: data/menus.ui:111 data/menus.ui:137 msgid "Lower" msgstr "Riduci" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the phone ringer #: data/menus.ui:118 data/menus.ui:144 src/service/plugins/telephony.js:194 msgid "Mute" msgstr "Silenzia" #: data/messaging.ui:12 src/service/plugins/sms.js:26 #: src/service/ui/messaging.js:911 msgid "Messaging" msgstr "Messaggi" #: data/messaging.ui:21 src/service/ui/messaging.js:992 msgid "New Conversation" msgstr "Nuova conversazione" #: data/messaging.ui:110 msgid "No conversation selected" msgstr "Nessuna conversazione selezionata" #: data/messaging.ui:126 msgid "Select or start a conversation" msgstr "Seleziona o inizia una conversazione" #: data/messaging.ui:193 data/notification.ui:52 data/telephony.ui:52 msgid "Device is disconnected" msgstr "Il dispositivo è disconnesso" #: data/messaging.ui:264 msgid "No Conversations" msgstr "Nessuna conversazione" #: data/notification.ui:21 data/telephony.ui:21 #: src/service/plugins/share.js:457 msgid "Send" msgstr "Invia" #: data/settings.ui:10 msgid "Searching for devices…" msgstr "Ricerca dei dispositivi…" #: data/settings.ui:49 data/settings.ui:63 msgid "Refresh" msgstr "Aggiorna" #: data/settings.ui:74 data/settings.ui:91 src/extension.js:105 msgid "Mobile Settings" msgstr "Impostazioni mobile" #: data/settings.ui:144 data/settings.ui:158 msgid "Edit Device Name" msgstr "Modifica il nome del dispositivo" #: data/settings.ui:236 msgid "Devices" msgstr "Dispositivi" #: data/settings.ui:299 msgid "Browser Add-Ons" msgstr "Estensioni browser" #: data/settings.ui:579 msgid "Enable" msgstr "Abilita" #: data/settings.ui:611 msgid "This device is invisible to unpaired devices" msgstr "Questo dispositivo è invisibile ai dispositivi disaccoppiati" #: data/settings.ui:623 src/service/daemon.js:532 msgid "Discovery Disabled" msgstr "Ricerca disattiva" #. TRANSLATORS: Share URL by SMS #: data/telephony.ui:9 src/service/daemon.js:718 src/service/plugins/sms.js:50 #: webextension/gettext.js:39 msgid "Send SMS" msgstr "Invia SMS" #. Service Menu #: src/extension.js:79 src/extension.js:255 msgid "Mobile Devices" msgstr "Dispositivi mobili" #. TRANSLATORS: %d is the number of devices connected #: src/extension.js:253 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d connesso" msgstr[1] "%d connessi" #. TRANSLATORS: Top-level context menu item for GSConnect #: src/nautilus-gsconnect.py:112 webextension/gettext.js:31 msgid "Send To Mobile Device" msgstr "Invia al dispositivo mobile" #: src/service/daemon.js:372 msgid "Report" msgstr "Segnalazione" #: src/service/daemon.js:507 msgid "Authentication Failure" msgstr "Autenticazione fallita" #: src/service/daemon.js:516 msgid "Network Error" msgstr "Errore di rete" #: src/service/daemon.js:517 src/service/daemon.js:525 msgid "Click for help troubleshooting" msgstr "Click per la risoluzione del problema" #: src/service/daemon.js:524 msgid "PulseAudio Error" msgstr "Errore PulseAudio" #: src/service/daemon.js:533 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/daemon.js:541 #, javascript-format msgid "%s Plugin Failed To Load" msgstr "Fallito caricamento del plugin %s" #: src/service/daemon.js:542 msgid "Click for more information" msgstr "Click per altre informazioni" #: src/service/daemon.js:724 msgid "Dial Number" msgstr "Componi numero" #: src/service/daemon.js:730 src/service/plugins/share.js:27 msgid "Share File" msgstr "Invia file" #: src/service/device.js:161 msgid "Not available" msgstr "Non disponibile" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:165 #, javascript-format msgid "Bluetooth device at %s" msgstr "Dispositivo Bluetooth a %s" #. TRANSLATORS: Label for TLS Certificate fingerprint #. #. Example: #. #. Google Pixel Fingerprint: #. 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 #: src/service/device.js:183 src/service/device.js:185 #, javascript-format msgid "%s Fingerprint:" msgstr "%s fingerprint:" #: src/service/device.js:242 msgid "Laptop" msgstr "Laptop" #: src/service/device.js:244 msgid "Smartphone" msgstr "Smartphone" #: src/service/device.js:246 msgid "Tablet" msgstr "Tablet" #: src/service/device.js:248 msgid "Desktop" msgstr "Desktop" #: src/service/device.js:408 src/service/ui/device.js:15 msgid "Reconnect" msgstr "Riconnetti" #: src/service/device.js:416 src/service/ui/device.js:16 #: src/service/ui/settings.js:371 msgid "Settings" msgstr "Impostazioni" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:607 #, javascript-format msgid "Pair Request from %s" msgstr "Richiesta di accoppiamento da %s" #: src/service/device.js:614 msgid "Reject" msgstr "Rifiuta" #: src/service/device.js:619 msgid "Accept" msgstr "Accetta" #: src/service/plugins/battery.js:188 src/service/plugins/findmyphone.js:19 msgid "Ring" msgstr "Squilla" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:197 #, javascript-format msgid "%s: Battery is low" msgstr "%s: batteria quasi scarica" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:199 #, javascript-format msgid "%d%% remaining" msgstr "%d%% rimanenti" #: src/service/plugins/clipboard.js:11 msgid "Clipboard" msgstr "Appunti" #: src/service/plugins/clipboard.js:17 msgid "Clipboard Push" msgstr "Invia appunti" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Pull" msgstr "Ricevi appunti" #: src/service/plugins/contacts.js:12 msgid "Contacts" msgstr "Contatti" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #: src/service/plugins/contacts.js:263 src/service/plugins/telephony.js:173 #: src/service/plugins/telephony.js:224 src/service/plugins/telephony.js:264 #: src/service/ui/contacts.js:156 src/service/ui/contacts.js:397 msgid "Unknown Contact" msgstr "Contatto sconosciuto" #: src/service/plugins/findmyphone.js:13 msgid "Find My Phone" msgstr "Trova il mio telefono" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Mousepad" #: src/service/plugins/mousepad.js:28 src/service/plugins/mousepad.js:569 msgid "Keyboard" msgstr "Tastiera" #: src/service/plugins/mousepad.js:223 msgid "Additional Software Required" msgstr "Software addizionale richiesto" #: src/service/plugins/mousepad.js:586 msgid "Keyboard not ready" msgstr "Tastiera non pronta" #: src/service/plugins/mpris.js:10 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/notification.js:26 msgid "Cancel Notification" msgstr "Cancella notifica" #: src/service/plugins/notification.js:34 msgid "Close Notification" msgstr "Chiudi notifica" #: src/service/plugins/notification.js:42 msgid "Reply Notification" msgstr "Rispondi alla notifica" #: src/service/plugins/notification.js:50 msgid "Send Notification" msgstr "Invia notifica" #: src/service/plugins/ping.js:11 src/service/plugins/ping.js:17 #: src/service/plugins/ping.js:46 msgid "Ping" 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:53 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/runcommand.js:12 msgid "Run Commands" msgstr "Esegui comandi" #: src/service/plugins/sftp.js:12 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:18 msgid "Mount" msgstr "Monta" #: src/service/plugins/sftp.js:26 msgid "Unmount" msgstr "Smonta" #: src/service/plugins/sftp.js:159 msgid "All files" msgstr "Tutti i file" #: src/service/plugins/sftp.js:160 msgid "Camera pictures" msgstr "Immagini fotocamera" #: src/service/plugins/sftp.js:417 msgid "Files" msgstr "File" #: src/service/plugins/share.js:13 src/service/plugins/share.js:19 msgid "Share" msgstr "Invia file" #: src/service/plugins/share.js:35 msgid "Share Text" msgstr "Invia testo" #: src/service/plugins/share.js:43 src/service/ui/messaging.js:975 #: src/service/ui/messaging.js:983 msgid "Share Link" msgstr "Condividi collegamento" #: src/service/plugins/share.js:132 src/service/plugins/share.js:303 msgid "Starting Transfer" msgstr "Avvia trasferimento" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:134 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Ricezione in corso di \"%s\" da %s" #: src/service/plugins/share.js:172 src/service/plugins/share.js:327 msgid "Transfer Successful" msgstr "Trasferimento riuscito" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:174 #, javascript-format msgid "Received “%s” from %s" msgstr "Ricevuto \"%s\" da %s" #: src/service/plugins/share.js:180 msgid "Open Folder" msgstr "Apri cartella" #: src/service/plugins/share.js:185 msgid "Open File" msgstr "Apri file" #: src/service/plugins/share.js:192 src/service/plugins/share.js:335 msgid "Transfer Failed" msgstr "Trasferimento fallito" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:194 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Ricezione fallita di \"%s\" da %s" #: src/service/plugins/share.js:233 #, 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:305 #, 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:329 #, 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:337 #, 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:408 #, 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:412 msgid "Open when done" msgstr "Apri al termine" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:451 #, javascript-format msgid "Send a link to %s" msgstr "Invia collegamento a %s" #: src/service/plugins/sms.js:13 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:34 msgid "New SMS (URI)" msgstr "Nuovo SMS (URI)" #: src/service/plugins/sms.js:42 src/service/plugins/telephony.js:22 msgid "Reply SMS" msgstr "Rispondi all'SMS" #: src/service/plugins/sms.js:58 msgid "Share SMS" msgstr "Condividi SMS" #: src/service/plugins/systemvolume.js:11 msgid "System Volume" msgstr "Volume di sistema" #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Silenzia chiamata" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:190 msgid "Incoming call" msgstr "Chiamata in arrivo" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:205 msgid "Ongoing call" msgstr "Chiamata in corso" #. TRANSLATORS: All other phone number types #: src/service/ui/contacts.js:118 src/service/ui/contacts.js:139 #, javascript-format msgid "%s・Other" msgstr "%s・Altro" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:123 #, javascript-format msgid "%s・Fax" msgstr "%s・Fax" #. TRANSLATORS: A work phone number #: src/service/ui/contacts.js:127 #, javascript-format msgid "%s・Work" msgstr "%s・Lavoro" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:131 #, javascript-format msgid "%s・Mobile" msgstr "%s・Mobile" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:135 #, javascript-format msgid "%s・Home" msgstr "%s・Casa" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #: src/service/ui/contacts.js:278 src/service/ui/contacts.js:292 #, javascript-format msgid "Send to %s" msgstr "Invia a %s" #: src/service/ui/device.js:608 msgid "Open" msgstr "Apri" #: src/service/ui/device.js:660 src/service/ui/device.js:673 msgid "On" msgstr "On" #: src/service/ui/device.js:660 src/service/ui/device.js:673 msgid "Off" msgstr "Off" #: src/service/ui/device.js:798 src/service/ui/device.js:826 #: src/service/ui/device.js:850 msgid "Disabled" msgstr "Disabilitato" #. TRANSLATORS: Title of keyboard shortcut dialog #: src/service/ui/keybindings.js:33 msgid "Set Shortcut" msgstr "Imposta scorciatoia" #. TRANSLATORS: Button to confirm the new shortcut #: src/service/ui/keybindings.js:47 msgid "Set" msgstr "Imposta" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/service/ui/keybindings.js:54 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Inserisci una nuova scorciatoia per %s" #. TRANSLATORS: Keys for cancelling (␛) or resetting (␈) a shortcut #: src/service/ui/keybindings.js:83 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "Premi ESC per annullare o Backspace per ripristinare la scorciatoia." #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/service/ui/keybindings.js:182 #, javascript-format msgid "%s is already being used" msgstr "%s già in uso" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:65 src/service/ui/messaging.js:98 msgid "Just now" msgstr "Proprio ora" #. TRANSLATORS: Time duration in minutes (eg. 15 minutes) #: src/service/ui/messaging.js:70 src/service/ui/messaging.js:102 #: src/shell/donotdisturb.js:266 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minuto" msgstr[1] "%d minuti" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:75 #, javascript-format msgid "Yesterday・%s" msgstr "Ieri・%s" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:243 #, javascript-format msgid "You: %s" msgstr "Tu: %s" #: src/service/ui/service.js:31 msgid "Select a Device" msgstr "Seleziona un dispositivo" #: src/service/ui/service.js:35 msgid "Select" msgstr "Seleziona" #: src/service/ui/settings.js:331 msgid "Unpaired" msgstr "Disaccoppiato" #: src/service/ui/settings.js:333 msgid "Disconnected" msgstr "Disconnesso" #: src/service/ui/settings.js:336 msgid "Connected" msgstr "Connesso" #. TRANSLATORS: Description of where directly shared files are stored. #: src/service/ui/settings.js:406 #, javascript-format msgid "Transferred files are placed in the Downloads folder." msgstr "I file trasferiti sono collocati nella cartella Scaricati." #: src/service/ui/settings.js:499 msgid "A complete KDE Connect implementation for GNOME" msgstr "Un'implementazione completa di KDE Connect per GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/service/ui/settings.js:508 msgid "translator-credits" msgstr "Jimmy Scionti " #: src/service/ui/settings.js:537 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/service/ui/settings.js:540 msgid "Review Log" msgstr "Log delle Revisioni" #. TRANSLATORS: When the battery level is 100% #: src/shell/device.js:82 msgid "Fully Charged" msgstr "Completamente carica" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:86 #, 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:96 #, 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:104 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d Rimanenti)" #: src/shell/donotdisturb.js:150 src/shell/donotdisturb.js:289 msgid "Do Not Disturb" msgstr "Non disturbare" #: src/shell/donotdisturb.js:156 msgid "Silence Mobile Device Notifications" msgstr "Silenzia le notifiche del dispositivo mobile" #: src/shell/donotdisturb.js:171 msgid "Until you turn off Do Not Disturb" msgstr "Fino a quando non disattivi Non disturbare" #. TRANSLATORS: Time until change with time duration #. EXAMPLE: Until 10:00 (2 hours) #: src/shell/donotdisturb.js:184 src/shell/donotdisturb.js:278 #, javascript-format msgid "Until %s (%s)" msgstr "Fino alle %s (%s)" #: src/shell/donotdisturb.js:216 msgid "Done" msgstr "Fatto" #. TRANSLATORS: Time duration in hours (eg. 2 hours) #: src/shell/donotdisturb.js:263 #, javascript-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "%d ora" msgstr[1] "%d ore" #: src/shell/notification.js:42 msgid "Reply" msgstr "Rispondi" #. TRANSLATORS: Extension name #: webextension/gettext.js:27 msgid "GSConnect" msgstr "GSConnect" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:29 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:33 msgid "Service Unavailable" msgstr "Servizio non disponibile" #. TRANSLATORS: No devices are known or available #: webextension/gettext.js:35 msgid "No Device Found" msgstr "Dispositivo non trovato" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:37 msgid "Open in Browser" msgstr "Apri nel browser" gnome-shell-extension-gsconnect-20/po/ja.po000066400000000000000000000540671341554142200210670ustar00rootroot00000000000000# 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. # msgid "" msgstr "" "Project-Id-Version: org.gnome.Shell.Extensions.GSConnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-11-19 11:14-0800\n" "PO-Revision-Date: 2018-05-24 00:30+0900\n" "Last-Translator: \n" "Language-Team: \n" "Language: ja\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Poedit 2.0.6\n" #: data/conversation.ui:71 data/conversation.ui:79 #, fuzzy msgid "Type a message" msgstr "SMSメッセージを入力" #: data/contacts.ui:51 data/contacts.ui:52 msgid "Type a phone number or name" msgstr "電話番号または名前を入力" #: data/device.ui:68 src/service/plugins/battery.js:12 msgid "Battery" msgstr "電池" #: data/device.ui:116 #, fuzzy msgid "Clipboard Sync" msgstr "クリップボード" #: data/device.ui:172 #, fuzzy msgid "Media Players" msgstr "メディアプレーヤーコントロール" #: data/device.ui:220 msgid "Mouse & Keyboard" msgstr "" #: data/device.ui:268 msgid "Volume Control" msgstr "" #: data/device.ui:308 data/device.ui:1406 #, fuzzy msgid "Sharing" msgstr "リンクを共有" #: data/device.ui:337 data/device.ui:534 data/device.ui:1449 #: src/service/plugins/runcommand.js:18 src/service/plugins/runcommand.js:169 #, fuzzy msgid "Commands" msgstr "コマンド" #: data/device.ui:385 msgid "Name" msgstr "名前" #: data/device.ui:401 data/device.ui:402 msgid "Choose an executable" msgstr "" #: data/device.ui:403 #, fuzzy msgid "Command Line" msgstr "コマンド" #: data/device.ui:597 #, fuzzy msgid "Share Notifications" msgstr "通知を送信" #: data/device.ui:636 msgid "Applications" msgstr "アプリケーション" #: data/device.ui:675 data/device.ui:1492 #: src/service/plugins/notification.js:12 msgid "Notifications" msgstr "通知" #: data/device.ui:705 msgid "Incoming Calls" msgstr "着信中" #: data/device.ui:755 data/device.ui:900 #, fuzzy msgid "Volume" msgstr "システムの音量" #: data/device.ui:813 data/device.ui:958 msgid "Pause Media" msgstr "メディアを一時停止" #: data/device.ui:852 #, fuzzy msgid "Ongoing Calls" msgstr "着信中" #: data/device.ui:1007 msgid "Mute Microphone" msgstr "マイクをミュート" #: data/device.ui:1047 data/device.ui:1535 src/service/plugins/telephony.js:12 msgid "Telephony" msgstr "電話" #: data/device.ui:1082 #, fuzzy msgid "Action Shortcuts" msgstr "キーボードショートカット" #: data/device.ui:1094 data/device.ui:1157 msgid "Reset All…" msgstr "" #: data/device.ui:1145 #, fuzzy msgid "Command Shortcuts" msgstr "キーボードショートカット" #: data/device.ui:1203 #, fuzzy msgid "Shortcuts" msgstr "キーボードショートカット" #: data/device.ui:1234 msgid "Plugins" msgstr "プラグイン" #: data/device.ui:1295 msgid "Delete" msgstr "" #: data/device.ui:1320 #, fuzzy msgid "Delete this device" msgstr "%sがこの端末を探すよう要求しました" #: data/device.ui:1335 msgid "Unpair and remove all settings and files" msgstr "" #: data/device.ui:1365 data/device.ui:1621 msgid "Advanced" msgstr "" #: data/device.ui:1578 #, fuzzy msgid "Keyboard Shortcuts" msgstr "キーボードショートカット" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/menus.ui:7 src/service/ui/service.js:115 #, fuzzy msgid "Connect to…" msgstr "GSConnect" #: data/menus.ui:13 data/settings.ui:881 msgid "Help" msgstr "" #. TRANSLATORS: Open the developer's dialog #: data/menus.ui:19 #, fuzzy msgid "Debugger" msgstr "デバッグモード" #: data/menus.ui:23 msgid "About" msgstr "概要" #. TRANSLATORS: Change the connection type to Bluetooth #: data/menus.ui:34 msgid "Switch to Bluetooth" msgstr "" #. TRANSLATORS: Change the connection type to TCP/IP #: data/menus.ui:41 msgid "Switch to LAN" msgstr "" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/menus.ui:48 src/service/ui/settings.js:612 msgid "Encryption Info" msgstr "" #. TRANSLATORS: Send a pair request to the device #: data/menus.ui:53 src/service/device.js:425 msgid "Pair" msgstr "" #. TRANSLATORS: Unpair the device and notify it #: data/menus.ui:59 src/service/device.js:433 #, fuzzy msgid "Unpair" msgstr "ペア解除した端末を表示" #. TRANSLATORS: Send clipboard content to device #: data/menus.ui:71 #, fuzzy msgid "To Device" msgstr "端末" #. TRANSLATORS: Receive clipboard content from the device #: data/menus.ui:77 #, fuzzy msgid "From Device" msgstr "端末を探す" #. TRANSLATORS: Don't change the system volume #: data/menus.ui:89 data/menus.ui:115 msgid "Nothing" msgstr "何もしない" #. TRANSLATORS: Lower the system volume #: data/menus.ui:96 data/menus.ui:122 msgid "Lower" msgstr "下げる" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the phone ringer #: data/menus.ui:103 data/menus.ui:129 src/service/plugins/telephony.js:176 msgid "Mute" msgstr "ミュート" #: data/messaging.ui:12 src/service/plugins/sms.js:25 #: src/service/ui/messaging.js:871 #, fuzzy msgid "Messaging" msgstr "メッセージ" #: data/messaging.ui:110 #, fuzzy msgid "Select a conversation" msgstr "他の人を招待して会議を始める" #: data/messaging.ui:188 msgid "Device is disconnected" msgstr "端末は切断" #: data/settings.ui:333 msgid "Appearance" msgstr "外観" #: data/settings.ui:383 msgid "Display Mode" msgstr "" #: data/settings.ui:425 #, fuzzy msgid "Service" msgstr "端末" #: data/settings.ui:475 msgid "Discoverable" msgstr "" #: data/settings.ui:528 #, fuzzy msgid "Restart Service" msgstr "端末" #: data/settings.ui:581 data/settings.ui:1015 src/service/device.js:417 #, fuzzy msgid "Settings" msgstr "モバイル設定" #: data/settings.ui:639 msgid "Remote Filesystems" msgstr "" #: data/settings.ui:675 msgid "Sound Effects" msgstr "" #: data/settings.ui:712 #, fuzzy msgid "Extended Keyboard Support" msgstr "キーボードショートカット" #: data/settings.ui:749 #, fuzzy msgid "Desktop Contacts" msgstr "不明な連絡先" #: data/settings.ui:786 #, fuzzy msgid "Files Integration" msgstr "Nautilus統合" #: data/settings.ui:813 msgid "Additional Features" msgstr "" #: data/settings.ui:903 msgid "Browser Add-Ons" msgstr "" #: data/settings.ui:921 #, fuzzy msgid "KDE Connect" msgstr "GSConnect" #: data/settings.ui:956 data/settings.ui:1058 msgid "Other" msgstr "その他" #. TRANSLATORS: No devices are known or available #: data/settings.ui:1106 webextension/gettext.js:35 #, fuzzy msgid "No Device Found" msgstr "端末はペア解除" #: data/settings.ui:1142 msgid "Refresh" msgstr "更新" #. Service Menu #: src/extension.js:79 src/extension.js:259 msgid "Mobile Devices" msgstr "モバイル端末" #: src/extension.js:105 msgid "Mobile Settings" msgstr "モバイル設定" #. TRANSLATORS: Extension name #: src/extension.js:196 src/extension.js:311 src/service/daemon.js:95 #: src/service/daemon.js:413 webextension/gettext.js:27 msgid "GSConnect" msgstr "GSConnect" #. TRANSLATORS: %d is the number of devices connected #: src/extension.js:257 #, fuzzy, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "GSConnect" #. TRANSLATORS: Top-level context menu item for GSConnect #: src/nautilus-gsconnect.py:112 webextension/gettext.js:31 msgid "Send To Mobile Device" msgstr "モバイル端末に送信" #: src/service/daemon.js:406 #, fuzzy msgid "A complete KDE Connect implementation for GNOME" msgstr "GNOME Shellと統合されたKDE Connectの実装" #. TRANSLATORS: eg. 'Translator Name ' #: src/service/daemon.js:415 msgid "translator-credits" msgstr "" #: src/service/daemon.js:437 msgid "Report" msgstr "" #: src/service/daemon.js:567 msgid "Authentication Failure" msgstr "" #: src/service/daemon.js:576 msgid "Network Error" msgstr "" #: src/service/daemon.js:577 src/service/daemon.js:587 msgid "Click for help troubleshooting" msgstr "" #: src/service/daemon.js:586 msgid "PulseAudio Error" msgstr "" #: src/service/daemon.js:596 msgid "Discovery Disabled" msgstr "" #: src/service/daemon.js:597 msgid "" "Discovery has been disabled due to the number of devices on this network." msgstr "" #: src/service/daemon.js:599 src/service/daemon.js:609 msgid "Click to open preferences" msgstr "" #: src/service/daemon.js:608 msgid "Additional Software Required" msgstr "" #: src/service/daemon.js:617 #, javascript-format msgid "%s Plugin Failed To Load" msgstr "" #: src/service/daemon.js:618 src/service/daemon.js:635 msgid "Click for more information" msgstr "" #: src/service/daemon.js:633 msgid "Wayland Not Supported" msgstr "" #: src/service/daemon.js:634 msgid "Remote input not supported on Wayland" msgstr "" #. Create an urgent notification #: src/service/daemon.js:658 #, fuzzy, javascript-format msgid "GSConnect: %s" msgstr "GSConnect" #. TRANSLATORS: Share URL by SMS #: src/service/daemon.js:808 src/service/plugins/sms.js:49 #: webextension/gettext.js:39 msgid "Send SMS" msgstr "SMSを送信" #: src/service/daemon.js:812 #, fuzzy msgid "Dial Number" msgstr "電話番号をダイヤル" #: src/service/device.js:166 #, fuzzy msgid "Not available" msgstr "Gvcが利用できません" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:170 #, javascript-format msgid "Bluetooth device at %s" msgstr "" #. TRANSLATORS: Label for TLS Certificate fingerprint #. #. Example: #. #. Google Pixel Fingerprint: #. 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 #: src/service/device.js:188 src/service/device.js:190 #, javascript-format msgid "%s Fingerprint:" msgstr "" #: src/service/device.js:240 msgid "Laptop" msgstr "ラップトップ" #: src/service/device.js:242 msgid "Smartphone" msgstr "スマートフォン" #: src/service/device.js:244 msgid "Tablet" msgstr "タブレット" #: src/service/device.js:246 msgid "Desktop" msgstr "デスクトップ" #: src/service/device.js:409 #, fuzzy msgid "Reconnect" msgstr "GSConnect" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:606 #, javascript-format msgid "Pair Request from %s" msgstr "%sからのペアリクエスト" #: src/service/device.js:613 msgid "Reject" msgstr "拒否" #: src/service/device.js:618 msgid "Accept" msgstr "承認" #: src/service/plugins/battery.js:194 src/service/plugins/findmyphone.js:18 #, fuzzy msgid "Ring" msgstr "探す" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:203 #, javascript-format msgid "%s: Battery is low" msgstr "" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:205 #, fuzzy, javascript-format msgid "%d%% remaining" msgstr "%d%% (残り %d∶%02d)" #: src/service/plugins/clipboard.js:11 msgid "Clipboard" msgstr "クリップボード" #: src/service/plugins/clipboard.js:17 #, fuzzy msgid "Clipboard Push" msgstr "クリップボード" #: src/service/plugins/clipboard.js:25 #, fuzzy msgid "Clipboard Pull" msgstr "クリップボード" #: src/service/plugins/contacts.js:12 #, fuzzy msgid "Contacts" msgstr "不明な連絡先" #: src/service/plugins/findmyphone.js:12 msgid "Find My Phone" msgstr "" #: src/service/plugins/findmyphone.js:65 msgid "Locate Device" msgstr "端末を探す" #: src/service/plugins/findmyphone.js:66 #, javascript-format msgid "%s asked to locate this device" msgstr "%sがこの端末を探すよう要求しました" #: src/service/plugins/findmyphone.js:74 msgid "Found" msgstr "発見" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "" #: src/service/plugins/mousepad.js:28 src/service/plugins/mousepad.js:578 #, fuzzy msgid "Keyboard" msgstr "キーボードショートカット" #: src/service/plugins/mousepad.js:595 #, fuzzy msgid "Keyboard not ready" msgstr "キーボードショートカット" #: src/service/plugins/mpris.js:10 msgid "MPRIS" msgstr "" #: src/service/plugins/notification.js:25 #, fuzzy msgid "Cancel Notification" msgstr "通知を送信" #: src/service/plugins/notification.js:33 #, fuzzy msgid "Close Notification" msgstr "通知を送信" #: src/service/plugins/notification.js:41 #, fuzzy msgid "Reply Notification" msgstr "通知を送信" #: src/service/plugins/notification.js:49 #, fuzzy msgid "Send Notification" msgstr "通知を送信" #: src/service/plugins/ping.js:11 src/service/plugins/ping.js:17 #: src/service/plugins/ping.js:46 msgid "Ping" 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:53 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/runcommand.js:12 msgid "Run Commands" msgstr "コマンドを実行" #: src/service/plugins/sftp.js:12 msgid "SFTP" msgstr "" #: src/service/plugins/sftp.js:18 #, fuzzy msgid "Mount" msgstr "アンマウント" #: src/service/plugins/sftp.js:26 msgid "Unmount" msgstr "アンマウント" #: src/service/plugins/sftp.js:170 msgid "All files" msgstr "" #: src/service/plugins/sftp.js:171 msgid "Camera pictures" msgstr "" #: src/service/plugins/sftp.js:333 msgid "Files" msgstr "" #: src/service/plugins/share.js:12 src/service/plugins/share.js:18 msgid "Share" msgstr "共有" #: src/service/plugins/share.js:26 #, fuzzy msgid "Share File" msgstr "ファイル/URLを共有" #: src/service/plugins/share.js:34 #, fuzzy msgid "Share Text" msgstr "共有" #: src/service/plugins/share.js:42 src/service/ui/messaging.js:903 #: src/service/ui/messaging.js:911 msgid "Share Link" msgstr "リンクを共有" #: src/service/plugins/share.js:131 src/service/plugins/share.js:282 msgid "Starting Transfer" msgstr "転送開始" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:133 #, javascript-format msgid "Receiving “%s” from %s" msgstr "「%s」を%sから受信しています" #. Action Buttons #: src/service/plugins/share.js:138 src/service/plugins/share.js:289 #: src/service/plugins/share.js:407 src/service/ui/keybindings.js:44 #: src/service/ui/service.js:121 src/service/ui/service.js:300 #: src/service/ui/settings.js:873 src/shell/donotdisturb.js:215 msgid "Cancel" msgstr "キャンセル" #: src/service/plugins/share.js:149 src/service/plugins/share.js:303 msgid "Transfer Successful" msgstr "転送成功" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:151 #, javascript-format msgid "Received “%s” from %s" msgstr "「%s」を%sから受信しました" #: src/service/plugins/share.js:157 msgid "Open Folder" msgstr "フォルダーを開く" #: src/service/plugins/share.js:162 msgid "Open File" msgstr "ファイルを開く" #: src/service/plugins/share.js:169 src/service/plugins/share.js:311 msgid "Transfer Failed" msgstr "転送失敗" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:171 #, fuzzy, javascript-format msgid "Failed to receive “%s” from %s" msgstr "「%s」を%sから受信失敗: %s" #: src/service/plugins/share.js:211 #, javascript-format msgid "Text Shared By %s" msgstr "" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:284 #, javascript-format msgid "Sending “%s” to %s" msgstr "「%s」を%sに送信しています" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:305 #, 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:313 #, fuzzy, javascript-format msgid "Failed to send “%s” to %s" msgstr "「%s」を%sに送信失敗: %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:384 #, javascript-format msgid "Send files to %s" msgstr "%sにファイルを送信" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:402 #, javascript-format msgid "Send a link to %s" msgstr "%sにリンクを送信" #: src/service/plugins/share.js:408 msgid "Send" msgstr "送信" #: src/service/plugins/sms.js:12 msgid "SMS" msgstr "" #: src/service/plugins/sms.js:33 msgid "New SMS (URI)" msgstr "" #: src/service/plugins/sms.js:41 msgid "Reply SMS" msgstr "" #: src/service/plugins/sms.js:57 #, fuzzy msgid "Share SMS" msgstr "共有" #: src/service/plugins/systemvolume.js:11 msgid "System Volume" msgstr "システムの音量" #: src/service/plugins/telephony.js:21 #, fuzzy msgid "Mute Call" msgstr "不在着信" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #: src/service/plugins/telephony.js:155 src/service/ui/contacts.js:96 #: src/service/ui/contacts.js:350 msgid "Unknown Contact" msgstr "不明な連絡先" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:172 #, fuzzy msgid "Incoming call" msgstr "着信" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:187 #, fuzzy msgid "Ongoing call" msgstr "着信" #. TRANSLATORS: All other phone number types #: src/service/ui/contacts.js:58 src/service/ui/contacts.js:79 #, fuzzy, javascript-format msgid "%s・Other" msgstr "%s・その他" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:63 #, javascript-format msgid "%s・Fax" msgstr "" #. TRANSLATORS: A work phone number #: src/service/ui/contacts.js:67 #, fuzzy, javascript-format msgid "%s・Work" msgstr "%s・仕事" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:71 #, fuzzy, javascript-format msgid "%s・Mobile" msgstr "%s・モバイル" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:75 #, fuzzy, javascript-format msgid "%s・Home" msgstr "%s・家" #: src/service/ui/contacts.js:203 msgid "Select a contact or number" msgstr "" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #: src/service/ui/contacts.js:239 src/service/ui/contacts.js:253 #, javascript-format msgid "Send to %s" msgstr "%sに送信" #. TRANSLATORS: Title of keyboard shortcut dialog #: src/service/ui/keybindings.js:33 #, fuzzy msgid "Set Shortcut" msgstr "キーボードショートカット" #. TRANSLATORS: Button to confirm the new shortcut #: src/service/ui/keybindings.js:47 #, fuzzy msgid "Set" msgstr "選択" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/service/ui/keybindings.js:54 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "" #. TRANSLATORS: Keys for cancelling (␛) or resetting (␈) a shortcut #: src/service/ui/keybindings.js:83 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/service/ui/keybindings.js:182 #, javascript-format msgid "%s is already being used" msgstr "" #: src/service/ui/service.js:122 #, fuzzy msgid "Connect" msgstr "GSConnect" #: src/service/ui/service.js:294 msgid "Select a Device" msgstr "端末を選択" #: src/service/ui/service.js:298 msgid "Select" msgstr "選択" #: src/service/ui/settings.js:298 msgid "Panel" msgstr "" #: src/service/ui/settings.js:298 #, fuzzy msgid "User Menu" msgstr "メニューを開く" #: src/service/ui/settings.js:874 #, fuzzy msgid "Open" msgstr "ファイルを開く" #: src/service/ui/settings.js:931 src/service/ui/settings.js:944 msgid "On" msgstr "" #: src/service/ui/settings.js:931 src/service/ui/settings.js:944 msgid "Off" msgstr "" #: src/service/ui/settings.js:1065 src/service/ui/settings.js:1131 msgid "Disabled" msgstr "" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:93 src/service/ui/messaging.js:126 msgid "Just now" msgstr "" #. TRANSLATORS: Time duration in minutes (eg. 15 minutes) #: src/service/ui/messaging.js:98 src/service/ui/messaging.js:130 #: src/shell/donotdisturb.js:266 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:103 #, javascript-format msgid "Yesterday・%s" msgstr "" #: src/service/ui/messaging.js:920 msgid "New Message" msgstr "新しいメッセージ" #. TRANSLATORS: When the battery level is 100% #: src/shell/device.js:86 msgid "Fully Charged" msgstr "充電完了" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:90 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (計測中…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:100 #, 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:108 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (残り %d∶%02d)" #: src/shell/donotdisturb.js:150 src/shell/donotdisturb.js:289 msgid "Do Not Disturb" msgstr "" #: src/shell/donotdisturb.js:156 #, fuzzy msgid "Silence Mobile Device Notifications" msgstr "通知を受信" #: src/shell/donotdisturb.js:171 msgid "Until you turn off Do Not Disturb" msgstr "" #. TRANSLATORS: Time until change with time duration #. EXAMPLE: Until 10:00 (2 hours) #: src/shell/donotdisturb.js:184 src/shell/donotdisturb.js:278 #, javascript-format msgid "Until %s (%s)" msgstr "" #: src/shell/donotdisturb.js:216 msgid "Done" msgstr "" #. TRANSLATORS: Time duration in hours (eg. 2 hours) #: src/shell/donotdisturb.js:263 #, javascript-format msgid "One hour" msgid_plural "%d hours" msgstr[0] "" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:29 #, fuzzy msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "ウェブブラウザから端末や連絡先とリンクを共有" #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:33 #, fuzzy msgid "Service Unavailable" msgstr "Gvcが利用できません" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:37 #, fuzzy msgid "Open in Browser" msgstr "フォルダーを開く" gnome-shell-extension-gsconnect-20/po/lt.po000066400000000000000000000541461341554142200211120ustar00rootroot00000000000000# 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. # msgid "" msgstr "" "Project-Id-Version: org.gnome.Shell.Extensions.GSConnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-11-19 11:14-0800\n" "PO-Revision-Date: 2018-09-01 00:27+0300\n" "Last-Translator: Moo\n" "Language-Team: \n" "Language: lt\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" "%100<10 || n%100>=20) ? 1 : 2);\n" "X-Generator: Poedit 2.0.6\n" #: data/conversation.ui:71 data/conversation.ui:79 #, fuzzy msgid "Type a message" msgstr "Rašykite SMS žinutę" #: data/contacts.ui:51 data/contacts.ui:52 msgid "Type a phone number or name" msgstr "Įrašykite telefono numerį ar vardą" #: data/device.ui:68 src/service/plugins/battery.js:12 msgid "Battery" msgstr "Baterija" #: data/device.ui:116 #, fuzzy msgid "Clipboard Sync" msgstr "Iškarpinė" #: data/device.ui:172 #, fuzzy msgid "Media Players" msgstr "Medijos leistuvės valdymas" #: data/device.ui:220 msgid "Mouse & Keyboard" msgstr "" #: data/device.ui:268 msgid "Volume Control" msgstr "" #: data/device.ui:308 data/device.ui:1406 #, fuzzy msgid "Sharing" msgstr "Bendrinti nuorodą" #: data/device.ui:337 data/device.ui:534 data/device.ui:1449 #: src/service/plugins/runcommand.js:18 src/service/plugins/runcommand.js:169 #, fuzzy msgid "Commands" msgstr "Komanda" #: data/device.ui:385 msgid "Name" msgstr "Pavadinimas" #: data/device.ui:401 data/device.ui:402 msgid "Choose an executable" msgstr "" #: data/device.ui:403 #, fuzzy msgid "Command Line" msgstr "Komanda" #: data/device.ui:597 #, fuzzy msgid "Share Notifications" msgstr "Siųsti pranešimus" #: data/device.ui:636 msgid "Applications" msgstr "Programos" #: data/device.ui:675 data/device.ui:1492 #: src/service/plugins/notification.js:12 msgid "Notifications" msgstr "Pranešimai" #: data/device.ui:705 msgid "Incoming Calls" msgstr "Gaunami skambučiai" #: data/device.ui:755 data/device.ui:900 #, fuzzy msgid "Volume" msgstr "Sistemos garsis" #: data/device.ui:813 data/device.ui:958 msgid "Pause Media" msgstr "Pristabdyti mediją" #: data/device.ui:852 #, fuzzy msgid "Ongoing Calls" msgstr "Gaunami skambučiai" #: data/device.ui:1007 msgid "Mute Microphone" msgstr "Nutildyti mikrofoną" #: data/device.ui:1047 data/device.ui:1535 src/service/plugins/telephony.js:12 msgid "Telephony" msgstr "Telefonija" #: data/device.ui:1082 #, fuzzy msgid "Action Shortcuts" msgstr "Klaviatūros trumpiniai" #: data/device.ui:1094 data/device.ui:1157 msgid "Reset All…" msgstr "" #: data/device.ui:1145 #, fuzzy msgid "Command Shortcuts" msgstr "Klaviatūros trumpiniai" #: data/device.ui:1203 #, fuzzy msgid "Shortcuts" msgstr "Klaviatūros trumpiniai" #: data/device.ui:1234 msgid "Plugins" msgstr "Įskiepiai" #: data/device.ui:1295 msgid "Delete" msgstr "" #: data/device.ui:1320 #, fuzzy msgid "Delete this device" msgstr "%s paprašė nustatyti šio įrenginio vietą" #: data/device.ui:1335 msgid "Unpair and remove all settings and files" msgstr "" #: data/device.ui:1365 data/device.ui:1621 msgid "Advanced" msgstr "" #: data/device.ui:1578 #, fuzzy msgid "Keyboard Shortcuts" msgstr "Klaviatūros trumpiniai" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/menus.ui:7 src/service/ui/service.js:115 #, fuzzy msgid "Connect to…" msgstr "GSConnect" #: data/menus.ui:13 data/settings.ui:881 msgid "Help" msgstr "" #. TRANSLATORS: Open the developer's dialog #: data/menus.ui:19 #, fuzzy msgid "Debugger" msgstr "Derinimo veiksena" #: data/menus.ui:23 msgid "About" msgstr "Apie" #. TRANSLATORS: Change the connection type to Bluetooth #: data/menus.ui:34 msgid "Switch to Bluetooth" msgstr "" #. TRANSLATORS: Change the connection type to TCP/IP #: data/menus.ui:41 msgid "Switch to LAN" msgstr "" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/menus.ui:48 src/service/ui/settings.js:612 msgid "Encryption Info" msgstr "" #. TRANSLATORS: Send a pair request to the device #: data/menus.ui:53 src/service/device.js:425 msgid "Pair" msgstr "" #. TRANSLATORS: Unpair the device and notify it #: data/menus.ui:59 src/service/device.js:433 #, fuzzy msgid "Unpair" msgstr "Rodyti nesuporuotus" #. TRANSLATORS: Send clipboard content to device #: data/menus.ui:71 #, fuzzy msgid "To Device" msgstr "Įrenginiai" #. TRANSLATORS: Receive clipboard content from the device #: data/menus.ui:77 #, fuzzy msgid "From Device" msgstr "Nustatyti įrenginio vietą" #. TRANSLATORS: Don't change the system volume #: data/menus.ui:89 data/menus.ui:115 msgid "Nothing" msgstr "Nieko" #. TRANSLATORS: Lower the system volume #: data/menus.ui:96 data/menus.ui:122 msgid "Lower" msgstr "Sumažinti" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the phone ringer #: data/menus.ui:103 data/menus.ui:129 src/service/plugins/telephony.js:176 msgid "Mute" msgstr "Nutildyti" #: data/messaging.ui:12 src/service/plugins/sms.js:25 #: src/service/ui/messaging.js:871 #, fuzzy msgid "Messaging" msgstr "Žinutė" #: data/messaging.ui:110 #, fuzzy msgid "Select a conversation" msgstr "Norėdami pradėti pokalbį, pridėkite žmones" #: data/messaging.ui:188 msgid "Device is disconnected" msgstr "Įrenginys yra atsijungęs" #: data/settings.ui:333 msgid "Appearance" msgstr "Išvaizda" #: data/settings.ui:383 msgid "Display Mode" msgstr "" #: data/settings.ui:425 #, fuzzy msgid "Service" msgstr "Įrenginiai" #: data/settings.ui:475 msgid "Discoverable" msgstr "" #: data/settings.ui:528 #, fuzzy msgid "Restart Service" msgstr "Įrenginiai" #: data/settings.ui:581 data/settings.ui:1015 src/service/device.js:417 #, fuzzy msgid "Settings" msgstr "Mobiliųjų nustatymai" #: data/settings.ui:639 msgid "Remote Filesystems" msgstr "" #: data/settings.ui:675 msgid "Sound Effects" msgstr "" #: data/settings.ui:712 #, fuzzy msgid "Extended Keyboard Support" msgstr "Klaviatūros trumpiniai" #: data/settings.ui:749 #, fuzzy msgid "Desktop Contacts" msgstr "Nežinomas adresatas" #: data/settings.ui:786 #, fuzzy msgid "Files Integration" msgstr "Nautilus integracija" #: data/settings.ui:813 msgid "Additional Features" msgstr "" #: data/settings.ui:903 msgid "Browser Add-Ons" msgstr "" #: data/settings.ui:921 #, fuzzy msgid "KDE Connect" msgstr "GSConnect" #: data/settings.ui:956 data/settings.ui:1058 msgid "Other" msgstr "Kita" #. TRANSLATORS: No devices are known or available #: data/settings.ui:1106 webextension/gettext.js:35 #, fuzzy msgid "No Device Found" msgstr "Įrenginys yra išporuotas" #: data/settings.ui:1142 msgid "Refresh" msgstr "Įkelti iš naujo" #. Service Menu #: src/extension.js:79 src/extension.js:259 msgid "Mobile Devices" msgstr "Mobilieji įrenginiai" #: src/extension.js:105 msgid "Mobile Settings" msgstr "Mobiliųjų nustatymai" #. TRANSLATORS: Extension name #: src/extension.js:196 src/extension.js:311 src/service/daemon.js:95 #: src/service/daemon.js:413 webextension/gettext.js:27 msgid "GSConnect" msgstr "GSConnect" #. TRANSLATORS: %d is the number of devices connected #: src/extension.js:257 #, fuzzy, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "GSConnect" msgstr[1] "GSConnect" msgstr[2] "GSConnect" #. TRANSLATORS: Top-level context menu item for GSConnect #: src/nautilus-gsconnect.py:112 webextension/gettext.js:31 msgid "Send To Mobile Device" msgstr "Siųsti į mobilųjį įrenginį" #: src/service/daemon.js:406 #, fuzzy msgid "A complete KDE Connect implementation for GNOME" msgstr "KDE Connect įgyvendinimas naudojant GNOME apvalkalo integraciją" #. TRANSLATORS: eg. 'Translator Name ' #: src/service/daemon.js:415 msgid "translator-credits" msgstr "" #: src/service/daemon.js:437 msgid "Report" msgstr "" #: src/service/daemon.js:567 msgid "Authentication Failure" msgstr "" #: src/service/daemon.js:576 msgid "Network Error" msgstr "" #: src/service/daemon.js:577 src/service/daemon.js:587 msgid "Click for help troubleshooting" msgstr "" #: src/service/daemon.js:586 msgid "PulseAudio Error" msgstr "" #: src/service/daemon.js:596 msgid "Discovery Disabled" msgstr "" #: src/service/daemon.js:597 msgid "" "Discovery has been disabled due to the number of devices on this network." msgstr "" #: src/service/daemon.js:599 src/service/daemon.js:609 msgid "Click to open preferences" msgstr "" #: src/service/daemon.js:608 msgid "Additional Software Required" msgstr "" #: src/service/daemon.js:617 #, javascript-format msgid "%s Plugin Failed To Load" msgstr "" #: src/service/daemon.js:618 src/service/daemon.js:635 msgid "Click for more information" msgstr "" #: src/service/daemon.js:633 msgid "Wayland Not Supported" msgstr "" #: src/service/daemon.js:634 msgid "Remote input not supported on Wayland" msgstr "" #. Create an urgent notification #: src/service/daemon.js:658 #, fuzzy, javascript-format msgid "GSConnect: %s" msgstr "GSConnect" #. TRANSLATORS: Share URL by SMS #: src/service/daemon.js:808 src/service/plugins/sms.js:49 #: webextension/gettext.js:39 msgid "Send SMS" msgstr "Siųsti SMS" #: src/service/daemon.js:812 #, fuzzy msgid "Dial Number" msgstr "Rinkti telefono numerį" #: src/service/device.js:166 #, fuzzy msgid "Not available" msgstr "Gvc neprieinama" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:170 #, javascript-format msgid "Bluetooth device at %s" msgstr "" #. TRANSLATORS: Label for TLS Certificate fingerprint #. #. Example: #. #. Google Pixel Fingerprint: #. 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 #: src/service/device.js:188 src/service/device.js:190 #, javascript-format msgid "%s Fingerprint:" msgstr "" #: src/service/device.js:240 msgid "Laptop" msgstr "Nešiojamas kompiuteris" #: src/service/device.js:242 msgid "Smartphone" msgstr "Išmanusis telefonas" #: src/service/device.js:244 msgid "Tablet" msgstr "Planšetė" #: src/service/device.js:246 msgid "Desktop" msgstr "Stalinis kompiuteris" #: src/service/device.js:409 #, fuzzy msgid "Reconnect" msgstr "GSConnect" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:606 #, javascript-format msgid "Pair Request from %s" msgstr "Suporavimo užklausa nuo %s" #: src/service/device.js:613 msgid "Reject" msgstr "Atmesti" #: src/service/device.js:618 msgid "Accept" msgstr "Priimti" #: src/service/plugins/battery.js:194 src/service/plugins/findmyphone.js:18 #, fuzzy msgid "Ring" msgstr "Nustatyti vietą" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:203 #, javascript-format msgid "%s: Battery is low" msgstr "" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:205 #, fuzzy, javascript-format msgid "%d%% remaining" msgstr "%d%% (Liko %d∶%02d)" #: src/service/plugins/clipboard.js:11 msgid "Clipboard" msgstr "Iškarpinė" #: src/service/plugins/clipboard.js:17 #, fuzzy msgid "Clipboard Push" msgstr "Iškarpinė" #: src/service/plugins/clipboard.js:25 #, fuzzy msgid "Clipboard Pull" msgstr "Iškarpinė" #: src/service/plugins/contacts.js:12 #, fuzzy msgid "Contacts" msgstr "Nežinomas adresatas" #: src/service/plugins/findmyphone.js:12 msgid "Find My Phone" msgstr "" #: src/service/plugins/findmyphone.js:65 msgid "Locate Device" msgstr "Nustatyti įrenginio vietą" #: src/service/plugins/findmyphone.js:66 #, javascript-format msgid "%s asked to locate this device" msgstr "%s paprašė nustatyti šio įrenginio vietą" #: src/service/plugins/findmyphone.js:74 msgid "Found" msgstr "Rastas" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "" #: src/service/plugins/mousepad.js:28 src/service/plugins/mousepad.js:578 #, fuzzy msgid "Keyboard" msgstr "Klaviatūros trumpiniai" #: src/service/plugins/mousepad.js:595 #, fuzzy msgid "Keyboard not ready" msgstr "Klaviatūros trumpiniai" #: src/service/plugins/mpris.js:10 msgid "MPRIS" msgstr "" #: src/service/plugins/notification.js:25 #, fuzzy msgid "Cancel Notification" msgstr "Siųsti pranešimus" #: src/service/plugins/notification.js:33 #, fuzzy msgid "Close Notification" msgstr "Siųsti pranešimus" #: src/service/plugins/notification.js:41 #, fuzzy msgid "Reply Notification" msgstr "Siųsti pranešimus" #: src/service/plugins/notification.js:49 #, fuzzy msgid "Send Notification" msgstr "Siųsti pranešimus" #: src/service/plugins/ping.js:11 src/service/plugins/ping.js:17 #: src/service/plugins/ping.js:46 msgid "Ping" msgstr "Ryšio patikrinimas" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:53 #, javascript-format msgid "Ping: %s" msgstr "Ryšio patikrinimas: %s" #: src/service/plugins/runcommand.js:12 msgid "Run Commands" msgstr "Vykdyti komandas" #: src/service/plugins/sftp.js:12 msgid "SFTP" msgstr "" #: src/service/plugins/sftp.js:18 #, fuzzy msgid "Mount" msgstr "Atjungti" #: src/service/plugins/sftp.js:26 msgid "Unmount" msgstr "Atjungti" #: src/service/plugins/sftp.js:170 msgid "All files" msgstr "" #: src/service/plugins/sftp.js:171 msgid "Camera pictures" msgstr "" #: src/service/plugins/sftp.js:333 msgid "Files" msgstr "" #: src/service/plugins/share.js:12 src/service/plugins/share.js:18 msgid "Share" msgstr "Bendrinti" #: src/service/plugins/share.js:26 #, fuzzy msgid "Share File" msgstr "Bendrinti failą" #: src/service/plugins/share.js:34 #, fuzzy msgid "Share Text" msgstr "Bendrinti" #: src/service/plugins/share.js:42 src/service/ui/messaging.js:903 #: src/service/ui/messaging.js:911 msgid "Share Link" msgstr "Bendrinti nuorodą" #: src/service/plugins/share.js:131 src/service/plugins/share.js:282 msgid "Starting Transfer" msgstr "Pradedamas persiuntimas" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:133 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Gaunama „%s“ iš %s" #. Action Buttons #: src/service/plugins/share.js:138 src/service/plugins/share.js:289 #: src/service/plugins/share.js:407 src/service/ui/keybindings.js:44 #: src/service/ui/service.js:121 src/service/ui/service.js:300 #: src/service/ui/settings.js:873 src/shell/donotdisturb.js:215 msgid "Cancel" msgstr "Atsisakyti" #: src/service/plugins/share.js:149 src/service/plugins/share.js:303 msgid "Transfer Successful" msgstr "Persiuntimas sėkmingas" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:151 #, javascript-format msgid "Received “%s” from %s" msgstr "Gautas „%s“ iš %s" #: src/service/plugins/share.js:157 msgid "Open Folder" msgstr "Atverti aplanką" #: src/service/plugins/share.js:162 msgid "Open File" msgstr "Atverti failą" #: src/service/plugins/share.js:169 src/service/plugins/share.js:311 msgid "Transfer Failed" msgstr "Persiuntimas nepavyko" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:171 #, fuzzy, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Nepavyko gauti „%s“ iš %s: %s" #: src/service/plugins/share.js:211 #, javascript-format msgid "Text Shared By %s" msgstr "" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:284 #, 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:305 #, 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:313 #, fuzzy, javascript-format msgid "Failed to send “%s” to %s" msgstr "Nepavyko išsiųsti „%s“ į %s: %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:384 #, javascript-format msgid "Send files to %s" msgstr "Siųsti failus į %s" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:402 #, javascript-format msgid "Send a link to %s" msgstr "Siųsti nuorodą į %s" #: src/service/plugins/share.js:408 msgid "Send" msgstr "Siųsti" #: src/service/plugins/sms.js:12 msgid "SMS" msgstr "" #: src/service/plugins/sms.js:33 msgid "New SMS (URI)" msgstr "" #: src/service/plugins/sms.js:41 msgid "Reply SMS" msgstr "" #: src/service/plugins/sms.js:57 #, fuzzy msgid "Share SMS" msgstr "Bendrinti" #: src/service/plugins/systemvolume.js:11 msgid "System Volume" msgstr "Sistemos garsis" #: src/service/plugins/telephony.js:21 #, fuzzy msgid "Mute Call" msgstr "Neatsakytas skambutis" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #: src/service/plugins/telephony.js:155 src/service/ui/contacts.js:96 #: src/service/ui/contacts.js:350 msgid "Unknown Contact" msgstr "Nežinomas adresatas" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:172 #, fuzzy msgid "Incoming call" msgstr "Gaunamas skambutis" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:187 #, fuzzy msgid "Ongoing call" msgstr "Gaunamas skambutis" #. TRANSLATORS: All other phone number types #: src/service/ui/contacts.js:58 src/service/ui/contacts.js:79 #, fuzzy, javascript-format msgid "%s・Other" msgstr "%s・Kita" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:63 #, javascript-format msgid "%s・Fax" msgstr "%s・Faksas" #. TRANSLATORS: A work phone number #: src/service/ui/contacts.js:67 #, fuzzy, javascript-format msgid "%s・Work" msgstr "%s・Darbo" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:71 #, fuzzy, javascript-format msgid "%s・Mobile" msgstr "%s・Mobilusis" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:75 #, fuzzy, javascript-format msgid "%s・Home" msgstr "%s・Namų" #: src/service/ui/contacts.js:203 msgid "Select a contact or number" msgstr "" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #: src/service/ui/contacts.js:239 src/service/ui/contacts.js:253 #, javascript-format msgid "Send to %s" msgstr "Siųsti %s" #. TRANSLATORS: Title of keyboard shortcut dialog #: src/service/ui/keybindings.js:33 #, fuzzy msgid "Set Shortcut" msgstr "Klaviatūros trumpiniai" #. TRANSLATORS: Button to confirm the new shortcut #: src/service/ui/keybindings.js:47 #, fuzzy msgid "Set" msgstr "Pasirinkti" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/service/ui/keybindings.js:54 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "" #. TRANSLATORS: Keys for cancelling (␛) or resetting (␈) a shortcut #: src/service/ui/keybindings.js:83 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/service/ui/keybindings.js:182 #, javascript-format msgid "%s is already being used" msgstr "" #: src/service/ui/service.js:122 #, fuzzy msgid "Connect" msgstr "GSConnect" #: src/service/ui/service.js:294 msgid "Select a Device" msgstr "Pasirinkti įrenginį" #: src/service/ui/service.js:298 msgid "Select" msgstr "Pasirinkti" #: src/service/ui/settings.js:298 msgid "Panel" msgstr "" #: src/service/ui/settings.js:298 #, fuzzy msgid "User Menu" msgstr "Atverti meniu" #: src/service/ui/settings.js:874 #, fuzzy msgid "Open" msgstr "Atverti failą" #: src/service/ui/settings.js:931 src/service/ui/settings.js:944 msgid "On" msgstr "" #: src/service/ui/settings.js:931 src/service/ui/settings.js:944 msgid "Off" msgstr "" #: src/service/ui/settings.js:1065 src/service/ui/settings.js:1131 msgid "Disabled" msgstr "" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:93 src/service/ui/messaging.js:126 msgid "Just now" msgstr "" #. TRANSLATORS: Time duration in minutes (eg. 15 minutes) #: src/service/ui/messaging.js:98 src/service/ui/messaging.js:130 #: src/shell/donotdisturb.js:266 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "" msgstr[1] "" msgstr[2] "" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:103 #, javascript-format msgid "Yesterday・%s" msgstr "" #: src/service/ui/messaging.js:920 msgid "New Message" msgstr "Nauja žinutė" #. TRANSLATORS: When the battery level is 100% #: src/shell/device.js:86 msgid "Fully Charged" msgstr "Pilnai įkrauta" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:90 #, 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:100 #, 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:108 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (Liko %d∶%02d)" #: src/shell/donotdisturb.js:150 src/shell/donotdisturb.js:289 msgid "Do Not Disturb" msgstr "" #: src/shell/donotdisturb.js:156 #, fuzzy msgid "Silence Mobile Device Notifications" msgstr "Gauti pranešimus" #: src/shell/donotdisturb.js:171 msgid "Until you turn off Do Not Disturb" msgstr "" #. TRANSLATORS: Time until change with time duration #. EXAMPLE: Until 10:00 (2 hours) #: src/shell/donotdisturb.js:184 src/shell/donotdisturb.js:278 #, javascript-format msgid "Until %s (%s)" msgstr "" #: src/shell/donotdisturb.js:216 msgid "Done" msgstr "" #. TRANSLATORS: Time duration in hours (eg. 2 hours) #: src/shell/donotdisturb.js:263 #, javascript-format msgid "One hour" msgid_plural "%d hours" msgstr[0] "" msgstr[1] "" msgstr[2] "" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:29 #, fuzzy msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "Bendrinti nuorodas iš saityno naršyklės su įrenginiais ir adresatais" #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:33 #, fuzzy msgid "Service Unavailable" msgstr "Paslauga neprieinama" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:37 #, fuzzy msgid "Open in Browser" msgstr "Atverti aplanką" gnome-shell-extension-gsconnect-20/po/meson.build000066400000000000000000000002041341554142200222570ustar00rootroot00000000000000# build translations in LINGUAS i18n = import('i18n') i18n.gettext( 'org.gnome.Shell.Extensions.GSConnect', preset: 'glib' ) gnome-shell-extension-gsconnect-20/po/nl_BE.po000066400000000000000000000542701341554142200214500ustar00rootroot00000000000000# 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. # msgid "" msgstr "" "Project-Id-Version: org.gnome.Shell.Extensions.GSConnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-11-19 11:14-0800\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" #: data/conversation.ui:71 data/conversation.ui:79 msgid "Type a message" msgstr "Typ een bericht" #: data/contacts.ui:51 data/contacts.ui:52 msgid "Type a phone number or name" msgstr "Typ een telefoonnummer of naam" #: data/device.ui:68 src/service/plugins/battery.js:12 msgid "Battery" msgstr "Accu" #: data/device.ui:116 msgid "Clipboard Sync" msgstr "Klembordsynchronisatie" #: data/device.ui:172 msgid "Media Players" msgstr "Mediaspelers" #: data/device.ui:220 msgid "Mouse & Keyboard" msgstr "Muis en klavier" #: data/device.ui:268 msgid "Volume Control" msgstr "Volumebeheer" #: data/device.ui:308 data/device.ui:1406 msgid "Sharing" msgstr "Deling" #: data/device.ui:337 data/device.ui:534 data/device.ui:1449 #: src/service/plugins/runcommand.js:18 src/service/plugins/runcommand.js:169 msgid "Commands" msgstr "Commando's" #: data/device.ui:385 msgid "Name" msgstr "Naam" #: data/device.ui:401 data/device.ui:402 msgid "Choose an executable" msgstr "Kies een uitvoerbaar bestand" #: data/device.ui:403 msgid "Command Line" msgstr "Commandoregel" #: data/device.ui:597 msgid "Share Notifications" msgstr "Deelnotificaties" #: data/device.ui:636 msgid "Applications" msgstr "Apps" #: data/device.ui:675 data/device.ui:1492 #: src/service/plugins/notification.js:12 msgid "Notifications" msgstr "Notificaties" #: data/device.ui:705 msgid "Incoming Calls" msgstr "Inkomende oproepen" #: data/device.ui:755 data/device.ui:900 msgid "Volume" msgstr "Volume" #: data/device.ui:813 data/device.ui:958 msgid "Pause Media" msgstr "Pauzeer media" #: data/device.ui:852 msgid "Ongoing Calls" msgstr "In gesprek" #: data/device.ui:1007 msgid "Mute Microphone" msgstr "Microfoon toedoen" #: data/device.ui:1047 data/device.ui:1535 src/service/plugins/telephony.js:12 msgid "Telephony" msgstr "Telefonie" #: data/device.ui:1082 msgid "Action Shortcuts" msgstr "Actie-sneltoetsen" #: data/device.ui:1094 data/device.ui:1157 msgid "Reset All…" msgstr "Herstel alles…" #: data/device.ui:1145 msgid "Command Shortcuts" msgstr "Commando-sneltoetsen" #: data/device.ui:1203 msgid "Shortcuts" msgstr "Sneltoetsen" #: data/device.ui:1234 msgid "Plugins" msgstr "Invoegtoepassingen" #: data/device.ui:1295 msgid "Delete" msgstr "Verwijder" #: data/device.ui:1320 msgid "Delete this device" msgstr "Verwijder deze GSM" #: data/device.ui:1335 msgid "Unpair and remove all settings and files" msgstr "Koppel af en wis alle instellingen en bestanden" #: data/device.ui:1365 data/device.ui:1621 msgid "Advanced" msgstr "Geavanceerd" #: data/device.ui:1578 msgid "Keyboard Shortcuts" msgstr "Sneltoetsen" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/menus.ui:7 src/service/ui/service.js:115 msgid "Connect to…" msgstr "Verbind met…" #: data/menus.ui:13 data/settings.ui:881 msgid "Help" msgstr "Hulp" #. TRANSLATORS: Open the developer's dialog #: data/menus.ui:19 msgid "Debugger" msgstr "Foutopsporing" #: data/menus.ui:23 msgid "About" msgstr "Over" #. TRANSLATORS: Change the connection type to Bluetooth #: data/menus.ui:34 msgid "Switch to Bluetooth" msgstr "Schakel naar Bluetooth" #. TRANSLATORS: Change the connection type to TCP/IP #: data/menus.ui:41 msgid "Switch to LAN" msgstr "Schakel naar LAN" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/menus.ui:48 src/service/ui/settings.js:612 msgid "Encryption Info" msgstr "Encryptie-info" #. TRANSLATORS: Send a pair request to the device #: data/menus.ui:53 src/service/device.js:425 msgid "Pair" msgstr "Koppel aan" #. TRANSLATORS: Unpair the device and notify it #: data/menus.ui:59 src/service/device.js:433 msgid "Unpair" msgstr "Koppel af" #. TRANSLATORS: Send clipboard content to device #: data/menus.ui:71 msgid "To Device" msgstr "Naar GSM" #. TRANSLATORS: Receive clipboard content from the device #: data/menus.ui:77 msgid "From Device" msgstr "Van GSM" #. TRANSLATORS: Don't change the system volume #: data/menus.ui:89 data/menus.ui:115 msgid "Nothing" msgstr "Niets" #. TRANSLATORS: Lower the system volume #: data/menus.ui:96 data/menus.ui:122 msgid "Lower" msgstr "Verlagen" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the phone ringer #: data/menus.ui:103 data/menus.ui:129 src/service/plugins/telephony.js:176 msgid "Mute" msgstr "Toedoen" #: data/messaging.ui:12 src/service/plugins/sms.js:25 #: src/service/ui/messaging.js:871 msgid "Messaging" msgstr "Berichten" #: data/messaging.ui:110 msgid "Select a conversation" msgstr "Kies een conversatie" #: data/messaging.ui:188 msgid "Device is disconnected" msgstr "Toestel is niet geconnecteerd" #: data/settings.ui:333 msgid "Appearance" msgstr "Uiterlijk" #: data/settings.ui:383 msgid "Display Mode" msgstr "Weergavemodus" #: data/settings.ui:425 msgid "Service" msgstr "Dienst" #: data/settings.ui:475 msgid "Discoverable" msgstr "Zichtbaar" #: data/settings.ui:528 msgid "Restart Service" msgstr "Herstart dienst" #: data/settings.ui:581 data/settings.ui:1015 src/service/device.js:417 msgid "Settings" msgstr "Instellingen" #: data/settings.ui:639 msgid "Remote Filesystems" msgstr "Externe bestandssystemen" #: data/settings.ui:675 msgid "Sound Effects" msgstr "Geluidseffecten" #: data/settings.ui:712 msgid "Extended Keyboard Support" msgstr "Uitgebreide klavierondersteuning" #: data/settings.ui:749 msgid "Desktop Contacts" msgstr "Bureaubladcontacten" #: data/settings.ui:786 msgid "Files Integration" msgstr "Bestanden-integratie" #: data/settings.ui:813 msgid "Additional Features" msgstr "Additionele functionaliteiten" #: data/settings.ui:903 msgid "Browser Add-Ons" msgstr "Browser-add-ons" #: data/settings.ui:921 msgid "KDE Connect" msgstr "KDE Connect" #: data/settings.ui:956 data/settings.ui:1058 msgid "Other" msgstr "Ander" #. TRANSLATORS: No devices are known or available #: data/settings.ui:1106 webextension/gettext.js:35 msgid "No Device Found" msgstr "Geen toestel gevonden" #: data/settings.ui:1142 msgid "Refresh" msgstr "Herlaad" #. Service Menu #: src/extension.js:79 src/extension.js:259 msgid "Mobile Devices" msgstr "GSM's" #: src/extension.js:105 msgid "Mobile Settings" msgstr "GSM-instellingen" #. TRANSLATORS: Extension name #: src/extension.js:196 src/extension.js:311 src/service/daemon.js:95 #: src/service/daemon.js:413 webextension/gettext.js:27 msgid "GSConnect" msgstr "GSConnect" #. TRANSLATORS: %d is the number of devices connected #: src/extension.js:257 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d verbonden" msgstr[1] "%d verbonden" #. TRANSLATORS: Top-level context menu item for GSConnect #: src/nautilus-gsconnect.py:112 webextension/gettext.js:31 msgid "Send To Mobile Device" msgstr "Verzend naar GSM" #: src/service/daemon.js:406 msgid "A complete KDE Connect implementation for GNOME" msgstr "Volledige KDE Connect-implementatie voor GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/service/daemon.js:415 msgid "translator-credits" msgstr "Heimen Stoffels" #: src/service/daemon.js:437 msgid "Report" msgstr "Rapporteer" #: src/service/daemon.js:567 msgid "Authentication Failure" msgstr "Authenticatieprobleem" #: src/service/daemon.js:576 msgid "Network Error" msgstr "Netwerkfout" #: src/service/daemon.js:577 src/service/daemon.js:587 msgid "Click for help troubleshooting" msgstr "Klik voor probleemoplossing" #: src/service/daemon.js:586 msgid "PulseAudio Error" msgstr "PulseAudio-fout" #: src/service/daemon.js:596 msgid "Discovery Disabled" msgstr "Herkenning uitgeschakeld" #: src/service/daemon.js:597 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/daemon.js:599 src/service/daemon.js:609 msgid "Click to open preferences" msgstr "Klik om voorkeuren te openen" #: src/service/daemon.js:608 msgid "Additional Software Required" msgstr "Additionele software vereist" #: src/service/daemon.js:617 #, javascript-format msgid "%s Plugin Failed To Load" msgstr "Kon plug-in %s niet laden" #: src/service/daemon.js:618 src/service/daemon.js:635 msgid "Click for more information" msgstr "Klik voor meer informatie" #: src/service/daemon.js:633 msgid "Wayland Not Supported" msgstr "Wayland niet ondersteund" #: src/service/daemon.js:634 msgid "Remote input not supported on Wayland" msgstr "Externe input wordt niet ondersteund op Wayland" #. Create an urgent notification #: src/service/daemon.js:658 #, javascript-format msgid "GSConnect: %s" msgstr "GSConnect: %s" #. TRANSLATORS: Share URL by SMS #: src/service/daemon.js:808 src/service/plugins/sms.js:49 #: webextension/gettext.js:39 msgid "Send SMS" msgstr "Verzend SMS" #: src/service/daemon.js:812 msgid "Dial Number" msgstr "Telefoonoproep" #: src/service/device.js:166 msgid "Not available" msgstr "Onbeschikbaar" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:170 #, javascript-format msgid "Bluetooth device at %s" msgstr "Bluetooth-toestel op %s" #. TRANSLATORS: Label for TLS Certificate fingerprint #. #. Example: #. #. Google Pixel Fingerprint: #. 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 #: src/service/device.js:188 src/service/device.js:190 #, javascript-format msgid "%s Fingerprint:" msgstr "%s-vingerafdruk:" #: src/service/device.js:240 msgid "Laptop" msgstr "Laptop" #: src/service/device.js:242 msgid "Smartphone" msgstr "Smartphone" #: src/service/device.js:244 msgid "Tablet" msgstr "Tablet" #: src/service/device.js:246 msgid "Desktop" msgstr "Desktop" #: src/service/device.js:409 msgid "Reconnect" msgstr "Herverbinden" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:606 #, javascript-format msgid "Pair Request from %s" msgstr "Koppelaanvraag van %s" #: src/service/device.js:613 msgid "Reject" msgstr "Verwerp" #: src/service/device.js:618 msgid "Accept" msgstr "Accepteer" #: src/service/plugins/battery.js:194 src/service/plugins/findmyphone.js:18 #, fuzzy msgid "Ring" msgstr "Lokaliseer" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:203 #, javascript-format msgid "%s: Battery is low" msgstr "%s: batterij is laag" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:205 #, javascript-format msgid "%d%% remaining" msgstr "%d%% resterend" #: src/service/plugins/clipboard.js:11 msgid "Clipboard" msgstr "Klembord" #: src/service/plugins/clipboard.js:17 msgid "Clipboard Push" msgstr "Verstuur klembord" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Pull" msgstr "Haal klembord op" #: src/service/plugins/contacts.js:12 msgid "Contacts" msgstr "Contacten" #: src/service/plugins/findmyphone.js:12 msgid "Find My Phone" msgstr "Vind mijn telefoon" #: src/service/plugins/findmyphone.js:65 msgid "Locate Device" msgstr "Lokaliseer GSM" #: src/service/plugins/findmyphone.js:66 #, javascript-format msgid "%s asked to locate this device" msgstr "%s wil deze GSM lokaliseren" #: src/service/plugins/findmyphone.js:74 msgid "Found" msgstr "Gevonden" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Touchpad" #: src/service/plugins/mousepad.js:28 src/service/plugins/mousepad.js:578 msgid "Keyboard" msgstr "Klavier" #: src/service/plugins/mousepad.js:595 msgid "Keyboard not ready" msgstr "Klavier niet gereed" #: src/service/plugins/mpris.js:10 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/notification.js:25 msgid "Cancel Notification" msgstr "Annuleer notificatie" #: src/service/plugins/notification.js:33 msgid "Close Notification" msgstr "Sluit notificatie" #: src/service/plugins/notification.js:41 msgid "Reply Notification" msgstr "Antwoordnotificatie" #: src/service/plugins/notification.js:49 msgid "Send Notification" msgstr "Verzendnotificatie" #: src/service/plugins/ping.js:11 src/service/plugins/ping.js:17 #: src/service/plugins/ping.js:46 msgid "Ping" 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:53 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/runcommand.js:12 msgid "Run Commands" msgstr "Voer commando's uit" #: src/service/plugins/sftp.js:12 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:18 msgid "Mount" msgstr "Koppel aan" #: src/service/plugins/sftp.js:26 msgid "Unmount" msgstr "Koppel af" #: src/service/plugins/sftp.js:170 msgid "All files" msgstr "Alle bestanden" #: src/service/plugins/sftp.js:171 msgid "Camera pictures" msgstr "Camera-afbeeldingen" #: src/service/plugins/sftp.js:333 msgid "Files" msgstr "Bestanden" #: src/service/plugins/share.js:12 src/service/plugins/share.js:18 msgid "Share" msgstr "Deel" #: src/service/plugins/share.js:26 msgid "Share File" msgstr "Deel bestand" #: src/service/plugins/share.js:34 msgid "Share Text" msgstr "Deel tekst" #: src/service/plugins/share.js:42 src/service/ui/messaging.js:903 #: src/service/ui/messaging.js:911 msgid "Share Link" msgstr "Deel link" #: src/service/plugins/share.js:131 src/service/plugins/share.js:282 msgid "Starting Transfer" msgstr "Overzetten starten" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:133 #, javascript-format msgid "Receiving “%s” from %s" msgstr "“%s” ontvangen van %s" #. Action Buttons #: src/service/plugins/share.js:138 src/service/plugins/share.js:289 #: src/service/plugins/share.js:407 src/service/ui/keybindings.js:44 #: src/service/ui/service.js:121 src/service/ui/service.js:300 #: src/service/ui/settings.js:873 src/shell/donotdisturb.js:215 msgid "Cancel" msgstr "Annuleer" #: src/service/plugins/share.js:149 src/service/plugins/share.js:303 msgid "Transfer Successful" msgstr "Met succes overgezet" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:151 #, javascript-format msgid "Received “%s” from %s" msgstr "“%s” ontvangen van %s" #: src/service/plugins/share.js:157 msgid "Open Folder" msgstr "Open folder" #: src/service/plugins/share.js:162 msgid "Open File" msgstr "Open bestand" #: src/service/plugins/share.js:169 src/service/plugins/share.js:311 msgid "Transfer Failed" msgstr "Overzetten niet gelukt" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:171 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Kon “%s” niet ontvangen van %s" #: src/service/plugins/share.js:211 #, 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:284 #, 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:305 #, 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:313 #, 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:384 #, javascript-format msgid "Send files to %s" msgstr "Verzend bestanden naar %s" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:402 #, javascript-format msgid "Send a link to %s" msgstr "Verzend een link naar %s" #: src/service/plugins/share.js:408 msgid "Send" msgstr "Verzend" #: src/service/plugins/sms.js:12 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:33 msgid "New SMS (URI)" msgstr "Nieuwe SMS (URI)" #: src/service/plugins/sms.js:41 msgid "Reply SMS" msgstr "Beantwoord SMS" #: src/service/plugins/sms.js:57 msgid "Share SMS" msgstr "Deel SMS" #: src/service/plugins/systemvolume.js:11 msgid "System Volume" msgstr "Systeemvolume" #: src/service/plugins/telephony.js:21 msgid "Mute Call" msgstr "Demp oproep" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #: src/service/plugins/telephony.js:155 src/service/ui/contacts.js:96 #: src/service/ui/contacts.js:350 msgid "Unknown Contact" msgstr "Onbekend contact" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:172 msgid "Incoming call" msgstr "Inkomende oproep" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:187 msgid "Ongoing call" msgstr "In gesprek" #. TRANSLATORS: All other phone number types #: src/service/ui/contacts.js:58 src/service/ui/contacts.js:79 #, javascript-format msgid "%s・Other" msgstr "%s・Ander" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:63 #, javascript-format msgid "%s・Fax" msgstr "%s・Fax" #. TRANSLATORS: A work phone number #: src/service/ui/contacts.js:67 #, javascript-format msgid "%s・Work" msgstr "%s・Job" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:71 #, javascript-format msgid "%s・Mobile" msgstr "%s・GSM" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:75 #, javascript-format msgid "%s・Home" msgstr "%s・Thuis" #: src/service/ui/contacts.js:203 msgid "Select a contact or number" msgstr "Kies een contact of nummer" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #: src/service/ui/contacts.js:239 src/service/ui/contacts.js:253 #, javascript-format msgid "Send to %s" msgstr "Verzend naar %s" #. TRANSLATORS: Title of keyboard shortcut dialog #: src/service/ui/keybindings.js:33 msgid "Set Shortcut" msgstr "Stel sneltoets in" #. TRANSLATORS: Button to confirm the new shortcut #: src/service/ui/keybindings.js:47 msgid "Set" msgstr "Stel in" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/service/ui/keybindings.js:54 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Voer een nieuwe sneltoets in voor het wijzigen van %s" #. TRANSLATORS: Keys for cancelling (␛) or resetting (␈) a shortcut #: src/service/ui/keybindings.js:83 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." #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/service/ui/keybindings.js:182 #, javascript-format msgid "%s is already being used" msgstr "%s is al in gebruik" #: src/service/ui/service.js:122 msgid "Connect" msgstr "Verbind" #: src/service/ui/service.js:294 msgid "Select a Device" msgstr "Kies een GSM" #: src/service/ui/service.js:298 msgid "Select" msgstr "Kies" #: src/service/ui/settings.js:298 msgid "Panel" msgstr "Paneel" #: src/service/ui/settings.js:298 msgid "User Menu" msgstr "Gebruikersmenu" #: src/service/ui/settings.js:874 msgid "Open" msgstr "Open" #: src/service/ui/settings.js:931 src/service/ui/settings.js:944 msgid "On" msgstr "Aan" #: src/service/ui/settings.js:931 src/service/ui/settings.js:944 msgid "Off" msgstr "Uit" #: src/service/ui/settings.js:1065 src/service/ui/settings.js:1131 msgid "Disabled" msgstr "Uitgeschakeld" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:93 src/service/ui/messaging.js:126 msgid "Just now" msgstr "Zopas" #. TRANSLATORS: Time duration in minutes (eg. 15 minutes) #: src/service/ui/messaging.js:98 src/service/ui/messaging.js:130 #: src/shell/donotdisturb.js:266 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minuut" msgstr[1] "%d minuten" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:103 #, javascript-format msgid "Yesterday・%s" msgstr "Gisteren - %s" #: src/service/ui/messaging.js:920 msgid "New Message" msgstr "Nieuw bericht" #. TRANSLATORS: When the battery level is 100% #: src/shell/device.js:86 msgid "Fully Charged" msgstr "Volledig opgeladen" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:90 #, 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:100 #, 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:108 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d%02d resterend)" #: src/shell/donotdisturb.js:150 src/shell/donotdisturb.js:289 msgid "Do Not Disturb" msgstr "Stoor mij niet" #: src/shell/donotdisturb.js:156 msgid "Silence Mobile Device Notifications" msgstr "Geen GSM-notificaties" #: src/shell/donotdisturb.js:171 msgid "Until you turn off Do Not Disturb" msgstr "Totdat je Stoor mij niet uitschakelt" #. TRANSLATORS: Time until change with time duration #. EXAMPLE: Until 10:00 (2 hours) #: src/shell/donotdisturb.js:184 src/shell/donotdisturb.js:278 #, javascript-format msgid "Until %s (%s)" msgstr "Tot %s (%s)" #: src/shell/donotdisturb.js:216 msgid "Done" msgstr "Klaar" #. TRANSLATORS: Time duration in hours (eg. 2 hours) #: src/shell/donotdisturb.js:263 #, javascript-format msgid "One hour" msgid_plural "%d hours" msgstr[0] "Eén uur" msgstr[1] "%d uur" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:29 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:33 msgid "Service Unavailable" msgstr "Dienst onbeschikbaar" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:37 msgid "Open in Browser" msgstr "Open in browser" gnome-shell-extension-gsconnect-20/po/nl_NL.po000066400000000000000000000626351341554142200214770ustar00rootroot00000000000000# 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. # msgid "" msgstr "" "Project-Id-Version: org.gnome.Shell.Extensions.GSConnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-12-29 16:44-0800\n" "PO-Revision-Date: 2019-01-06 18:03+0100\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 2.1.1\n" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/connect.ui:24 data/menus.ui:7 msgid "Connect to…" msgstr "Verbinden met…" #. Action Buttons #: data/connect.ui:30 data/notification.ui:14 data/telephony.ui:14 #: src/service/plugins/share.js:139 src/service/plugins/share.js:310 #: src/service/plugins/share.js:456 src/service/ui/device.js:607 #: src/service/ui/keybindings.js:44 src/service/ui/service.js:37 #: src/service/ui/settings.js:539 src/shell/donotdisturb.js:215 msgid "Cancel" msgstr "Annuleren" #: data/connect.ui:37 msgid "Connect" msgstr "Verbinden" #: data/connect.ui:98 msgid "IP Address" msgstr "IP-adres" #: data/connect.ui:142 msgid "Bluetooth Device" msgstr "Bluetooth-apparaat" #: data/contacts.ui:49 msgid "Type a phone number or name" msgstr "Typ een telefoonnummer of naam" #: data/contacts.ui:83 msgid "No contacts" msgstr "Contactpersonen" #: data/contacts.ui:95 data/menus.ui:33 data/messaging.ui:247 msgid "Help" msgstr "Hulp" #: data/conversation.ui:76 data/conversation.ui:85 src/shell/notification.js:51 msgid "Type a message" msgstr "Typ een bericht" #: data/conversation.ui:84 msgid "Send Message" msgstr "Bericht versturen" #: data/device.ui:67 src/service/plugins/battery.js:12 msgid "Battery" msgstr "Batterij" #: data/device.ui:122 msgid "Clipboard Sync" msgstr "Klembordsynchronisatie" #: data/device.ui:185 msgid "Media Players" msgstr "Mediaspelers" #: data/device.ui:240 msgid "Mouse & Keyboard" msgstr "Muis en toetsenbord" #: data/device.ui:295 msgid "Volume Control" msgstr "Volumebeheer" #: data/device.ui:345 data/device.ui:1728 msgid "Sharing" msgstr "Delen" #: data/device.ui:374 data/device.ui:651 data/device.ui:1774 #: src/service/plugins/runcommand.js:18 src/service/plugins/runcommand.js:169 msgid "Commands" msgstr "Opdrachten" #: data/device.ui:424 data/device.ui:427 msgid "Name" msgstr "Naam" #: data/device.ui:440 data/device.ui:446 msgid "Command Line" msgstr "Opdrachtregel" #: data/device.ui:444 data/device.ui:445 msgid "Choose an executable" msgstr "Kies een uitvoerbaar bestand" #: data/device.ui:496 data/device.ui:511 msgid "Add" msgstr "Toevoegen" #: data/device.ui:527 data/device.ui:542 msgid "Remove" msgstr "Verwijderen" #: data/device.ui:576 data/device.ui:588 msgid "Edit" msgstr "Bewerken" #: data/device.ui:604 data/device.ui:616 msgid "Save" msgstr "Opslaan" #: data/device.ui:712 msgid "Share Notifications" msgstr "Deelmeldingen" #: data/device.ui:763 msgid "Applications" msgstr "Applicaties" #: data/device.ui:809 data/device.ui:1820 #: src/service/plugins/notification.js:13 msgid "Notifications" msgstr "Meldingen" #: data/device.ui:839 msgid "Incoming Calls" msgstr "Inkomende oproepen" #: data/device.ui:888 data/device.ui:1055 msgid "Volume" msgstr "Volume" #: data/device.ui:954 data/device.ui:1120 msgid "Pause Media" msgstr "Media pauzeren" #: data/device.ui:1007 msgid "Ongoing Calls" msgstr "Lopende gesprekken" #: data/device.ui:1176 msgid "Mute Microphone" msgstr "Microfoon dempen" #: data/device.ui:1230 data/device.ui:1866 src/service/plugins/telephony.js:13 msgid "Telephony" msgstr "Telefoon" #: data/device.ui:1265 msgid "Action Shortcuts" msgstr "Actie-sneltoetsen" #: data/device.ui:1280 data/device.ui:1349 msgid "Reset All…" msgstr "Standaardwaarden…" #: data/device.ui:1334 msgid "Command Shortcuts" msgstr "Opdracht-sneltoetsen" #: data/device.ui:1398 msgid "Shortcuts" msgstr "Sneltoetsen" #: data/device.ui:1429 msgid "Plugins" msgstr "Plug-ins" #: data/device.ui:1475 msgid "Experimental" msgstr "Experimenteel" #: data/device.ui:1524 msgid "Legacy SMS Support" msgstr "Verouderde SMS-ondersteuning" #: data/device.ui:1598 msgid "Delete" msgstr "Verwijderen" #: data/device.ui:1627 msgid "Delete this device" msgstr "Dit apparaat verwijderen" #: data/device.ui:1645 msgid "Unpair and remove all settings and files" msgstr "Ontkoppelen en alle instellingen en bestanden verwijderen" #: data/device.ui:1678 data/device.ui:1958 msgid "Advanced" msgstr "Geavanceerd" #: data/device.ui:1912 msgid "Keyboard Shortcuts" msgstr "Sneltoetsen" #. TRANSLATORS: Send a pair request to the device #: data/device.ui:2015 data/menus.ui:68 src/service/device.js:424 msgid "Pair" msgstr "Aankoppelen" #: data/device.ui:2047 msgid "Device is unpaired" msgstr "Apparaat is niet gekoppeld" #: data/device.ui:2062 msgid "You may configure this device before pairing" msgstr "Je kunt dit apparaat instellen alvorens het te koppelen" #: data/menus.ui:12 msgid "Display Mode" msgstr "Weergavemodus" #. TRANSLATORS: Show device indicators in the top bar #: data/menus.ui:15 msgid "Panel" msgstr "Paneel" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/menus.ui:21 msgid "User Menu" msgstr "Gebruikersmenu" #. TRANSLATORS: Generate a support log #: data/menus.ui:29 src/service/ui/settings.js:536 msgid "Generate Support Log" msgstr "Ondersteuningslogboek genereren" #: data/menus.ui:38 msgid "About" msgstr "Over" #. TRANSLATORS: Change the connection type to Bluetooth #: data/menus.ui:49 msgid "Switch to Bluetooth" msgstr "Overschakelen naar Bluetooth" #. TRANSLATORS: Change the connection type to TCP/IP #: data/menus.ui:56 msgid "Switch to LAN" msgstr "Overschakelen naar LAN" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/menus.ui:63 src/service/ui/device.js:317 msgid "Encryption Info" msgstr "Versleutelingsinformatie" #. TRANSLATORS: Unpair the device and notify it #: data/menus.ui:74 src/service/device.js:432 msgid "Unpair" msgstr "Ontkoppelen" #. TRANSLATORS: Send clipboard content to device #: data/menus.ui:86 msgid "To Device" msgstr "Naar apparaat" #. TRANSLATORS: Receive clipboard content from the device #: data/menus.ui:92 msgid "From Device" msgstr "Van apparaat" #. TRANSLATORS: Don't change the system volume #: data/menus.ui:104 data/menus.ui:130 msgid "Nothing" msgstr "Niets" #. TRANSLATORS: Lower the system volume #: data/menus.ui:111 data/menus.ui:137 msgid "Lower" msgstr "Verlagen" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the phone ringer #: data/menus.ui:118 data/menus.ui:144 src/service/plugins/telephony.js:194 msgid "Mute" msgstr "Dempen" #: data/messaging.ui:12 src/service/plugins/sms.js:26 #: src/service/ui/messaging.js:911 msgid "Messaging" msgstr "Berichten" #: data/messaging.ui:21 src/service/ui/messaging.js:992 msgid "New Conversation" msgstr "Nieuw gesprek" #: data/messaging.ui:110 msgid "No conversation selected" msgstr "Geen gesprek gekozen" #: data/messaging.ui:126 msgid "Select or start a conversation" msgstr "Kies of begin een gesprek" #: data/messaging.ui:193 data/notification.ui:52 data/telephony.ui:52 msgid "Device is disconnected" msgstr "Apparaat is niet verbonden" #: data/messaging.ui:264 msgid "No Conversations" msgstr "Geen gesprekken" #: data/notification.ui:21 data/telephony.ui:21 #: src/service/plugins/share.js:457 msgid "Send" msgstr "Versturen" #: data/settings.ui:10 msgid "Searching for devices…" msgstr "Bezig met zoeken naar apparaten…" #: data/settings.ui:49 data/settings.ui:63 msgid "Refresh" msgstr "Verversen" #: data/settings.ui:74 data/settings.ui:91 src/extension.js:105 msgid "Mobile Settings" msgstr "Mobiele instellingen" #: data/settings.ui:144 data/settings.ui:158 msgid "Edit Device Name" msgstr "Apparaatnaam bewerken" #: data/settings.ui:236 msgid "Devices" msgstr "Apparaten" #: data/settings.ui:299 msgid "Browser Add-Ons" msgstr "Browser-add-ons" #: data/settings.ui:579 msgid "Enable" msgstr "Inschakelen" #: data/settings.ui:611 msgid "This device is invisible to unpaired devices" msgstr "Dit apparaat is onzichtbaar voor niet-gekoppelde apparaten" #: data/settings.ui:623 src/service/daemon.js:532 msgid "Discovery Disabled" msgstr "Herkenning uitgeschakeld" #. TRANSLATORS: Share URL by SMS #: data/telephony.ui:9 src/service/daemon.js:718 src/service/plugins/sms.js:50 #: webextension/gettext.js:39 msgid "Send SMS" msgstr "SMS versturen" #. Service Menu #: src/extension.js:79 src/extension.js:255 msgid "Mobile Devices" msgstr "Mobiele apparaten" #. TRANSLATORS: %d is the number of devices connected #: src/extension.js:253 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d verbonden" msgstr[1] "%d verbonden" #. TRANSLATORS: Top-level context menu item for GSConnect #: src/nautilus-gsconnect.py:112 webextension/gettext.js:31 msgid "Send To Mobile Device" msgstr "Versturen naar mobiel apparaat" #: src/service/daemon.js:372 msgid "Report" msgstr "Rapporteren" #: src/service/daemon.js:507 msgid "Authentication Failure" msgstr "Authenticatiefout" #: src/service/daemon.js:516 msgid "Network Error" msgstr "Netwerkfout" #: src/service/daemon.js:517 src/service/daemon.js:525 msgid "Click for help troubleshooting" msgstr "Klik voor probleemoplossing" #: src/service/daemon.js:524 msgid "PulseAudio Error" msgstr "PulseAudio-fout" #: src/service/daemon.js:533 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/daemon.js:541 #, javascript-format msgid "%s Plugin Failed To Load" msgstr "Kan plug-in %s niet laden" #: src/service/daemon.js:542 msgid "Click for more information" msgstr "Klik voor meer informatie" #: src/service/daemon.js:724 msgid "Dial Number" msgstr "Nummer bellen" #: src/service/daemon.js:730 src/service/plugins/share.js:27 msgid "Share File" msgstr "Bestand delen" #: src/service/device.js:161 msgid "Not available" msgstr "Niet beschikbaar" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:165 #, javascript-format msgid "Bluetooth device at %s" msgstr "Bluetooth-apparaat op %s" #. TRANSLATORS: Label for TLS Certificate fingerprint #. #. Example: #. #. Google Pixel Fingerprint: #. 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 #: src/service/device.js:183 src/service/device.js:185 #, javascript-format msgid "%s Fingerprint:" msgstr "%s-vingerafdruk:" #: src/service/device.js:242 msgid "Laptop" msgstr "Laptop" #: src/service/device.js:244 msgid "Smartphone" msgstr "Smartphone" #: src/service/device.js:246 msgid "Tablet" msgstr "Tablet" #: src/service/device.js:248 msgid "Desktop" msgstr "Bureaubladcomputer" #: src/service/device.js:408 src/service/ui/device.js:15 msgid "Reconnect" msgstr "Opnieuw verbinden" #: src/service/device.js:416 src/service/ui/device.js:16 #: src/service/ui/settings.js:371 msgid "Settings" msgstr "Instellingen" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:607 #, javascript-format msgid "Pair Request from %s" msgstr "Koppelverzoek van %s" #: src/service/device.js:614 msgid "Reject" msgstr "Weigeren" #: src/service/device.js:619 msgid "Accept" msgstr "Accepteren" #: src/service/plugins/battery.js:188 src/service/plugins/findmyphone.js:19 msgid "Ring" msgstr "Over laten gaan" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:197 #, javascript-format msgid "%s: Battery is low" msgstr "%s: accuniveau is laag" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:199 #, javascript-format msgid "%d%% remaining" msgstr "%d%% resterend" #: src/service/plugins/clipboard.js:11 msgid "Clipboard" msgstr "Klembord" #: src/service/plugins/clipboard.js:17 msgid "Clipboard Push" msgstr "Klembord versturen" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Pull" msgstr "Klembord ophalen" #: src/service/plugins/contacts.js:12 msgid "Contacts" msgstr "Contactpersonen" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #: src/service/plugins/contacts.js:263 src/service/plugins/telephony.js:173 #: src/service/plugins/telephony.js:224 src/service/plugins/telephony.js:264 #: src/service/ui/contacts.js:156 src/service/ui/contacts.js:397 msgid "Unknown Contact" msgstr "Onbekende contactpersoon" #: src/service/plugins/findmyphone.js:13 msgid "Find My Phone" msgstr "Zoek mijn telefoon" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Touchpad" #: src/service/plugins/mousepad.js:28 src/service/plugins/mousepad.js:569 msgid "Keyboard" msgstr "Toetsenbord" #: src/service/plugins/mousepad.js:223 msgid "Additional Software Required" msgstr "Extra software vereist" #: src/service/plugins/mousepad.js:586 msgid "Keyboard not ready" msgstr "Toetsenbord niet gereed" #: src/service/plugins/mpris.js:10 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/notification.js:26 msgid "Cancel Notification" msgstr "Melding annuleren" #: src/service/plugins/notification.js:34 msgid "Close Notification" msgstr "Melding sluiten" #: src/service/plugins/notification.js:42 msgid "Reply Notification" msgstr "Antwoordmelding" #: src/service/plugins/notification.js:50 msgid "Send Notification" msgstr "Melding versturen" #: src/service/plugins/ping.js:11 src/service/plugins/ping.js:17 #: src/service/plugins/ping.js:46 msgid "Ping" 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:53 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/runcommand.js:12 msgid "Run Commands" msgstr "Opdrachten uitvoeren" #: src/service/plugins/sftp.js:12 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:18 msgid "Mount" msgstr "Aankoppelen" #: src/service/plugins/sftp.js:26 msgid "Unmount" msgstr "Ontkoppelen" #: src/service/plugins/sftp.js:159 msgid "All files" msgstr "Alle bestanden" #: src/service/plugins/sftp.js:160 msgid "Camera pictures" msgstr "Camera-afbeeldingen" #: src/service/plugins/sftp.js:417 msgid "Files" msgstr "Bestanden" #: src/service/plugins/share.js:13 src/service/plugins/share.js:19 msgid "Share" msgstr "Delen" #: src/service/plugins/share.js:35 msgid "Share Text" msgstr "Tekst delen" #: src/service/plugins/share.js:43 src/service/ui/messaging.js:975 #: src/service/ui/messaging.js:983 msgid "Share Link" msgstr "Link delen" #: src/service/plugins/share.js:132 src/service/plugins/share.js:303 msgid "Starting Transfer" msgstr "Bezig met starten van overdracht" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:134 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Bezig met ontvangen van “%s” van %s" #: src/service/plugins/share.js:172 src/service/plugins/share.js:327 msgid "Transfer Successful" msgstr "Overdracht voltooid" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:174 #, javascript-format msgid "Received “%s” from %s" msgstr "“%s” ontvangen van %s" #: src/service/plugins/share.js:180 msgid "Open Folder" msgstr "Map openen" #: src/service/plugins/share.js:185 msgid "Open File" msgstr "Bestand openen" #: src/service/plugins/share.js:192 src/service/plugins/share.js:335 msgid "Transfer Failed" msgstr "Overdracht mislukt" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:194 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Ontvangen van “%s” van %s mislukt" #: src/service/plugins/share.js:233 #, 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:305 #, 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:329 #, javascript-format msgid "Sent “%s” to %s" msgstr "“%s” verstuurd naar %s" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:337 #, 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:408 #, 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:412 msgid "Open when done" msgstr "Openen na afronden" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:451 #, javascript-format msgid "Send a link to %s" msgstr "Link versturen naar %s" #: src/service/plugins/sms.js:13 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:34 msgid "New SMS (URI)" msgstr "Nieuwe SMS (URI)" #: src/service/plugins/sms.js:42 src/service/plugins/telephony.js:22 msgid "Reply SMS" msgstr "SMS beantwoorden" #: src/service/plugins/sms.js:58 msgid "Share SMS" msgstr "SMS delen" #: src/service/plugins/systemvolume.js:11 msgid "System Volume" msgstr "Volumeniveau van systeem" #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Oproep dempen" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:190 msgid "Incoming call" msgstr "Inkomende oproep" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:205 msgid "Ongoing call" msgstr "Lopende oproep" #. TRANSLATORS: All other phone number types #: src/service/ui/contacts.js:118 src/service/ui/contacts.js:139 #, javascript-format msgid "%s・Other" msgstr "%s・Overig" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:123 #, javascript-format msgid "%s・Fax" msgstr "%s・Fax" #. TRANSLATORS: A work phone number #: src/service/ui/contacts.js:127 #, javascript-format msgid "%s・Work" msgstr "%s・Werk" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:131 #, javascript-format msgid "%s・Mobile" msgstr "%s・Mobiel" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:135 #, javascript-format msgid "%s・Home" msgstr "%s・Thuis" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #: src/service/ui/contacts.js:278 src/service/ui/contacts.js:292 #, javascript-format msgid "Send to %s" msgstr "Versturen naar %s" #: src/service/ui/device.js:608 msgid "Open" msgstr "Openen" #: src/service/ui/device.js:660 src/service/ui/device.js:673 msgid "On" msgstr "Aan" #: src/service/ui/device.js:660 src/service/ui/device.js:673 msgid "Off" msgstr "Uit" #: src/service/ui/device.js:798 src/service/ui/device.js:826 #: src/service/ui/device.js:850 msgid "Disabled" msgstr "Uitgeschakeld" #. TRANSLATORS: Title of keyboard shortcut dialog #: src/service/ui/keybindings.js:33 msgid "Set Shortcut" msgstr "Sneltoets instellen" #. TRANSLATORS: Button to confirm the new shortcut #: src/service/ui/keybindings.js:47 msgid "Set" msgstr "Instellen" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/service/ui/keybindings.js:54 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Voer een nieuwe sneltoets in om %s te wijzigen" #. TRANSLATORS: Keys for cancelling (␛) or resetting (␈) a shortcut #: src/service/ui/keybindings.js:83 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." #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/service/ui/keybindings.js:182 #, javascript-format msgid "%s is already being used" msgstr "%s wordt al gebruikt" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:65 src/service/ui/messaging.js:98 msgid "Just now" msgstr "Zojuist" #. TRANSLATORS: Time duration in minutes (eg. 15 minutes) #: src/service/ui/messaging.js:70 src/service/ui/messaging.js:102 #: src/shell/donotdisturb.js:266 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minuut" msgstr[1] "%d minuten" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:75 #, javascript-format msgid "Yesterday・%s" msgstr "Gisteren - %s" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:243 #, javascript-format msgid "You: %s" msgstr "Jij: %s" #: src/service/ui/service.js:31 msgid "Select a Device" msgstr "Kies een apparaat" #: src/service/ui/service.js:35 msgid "Select" msgstr "Kiezen" #: src/service/ui/settings.js:331 msgid "Unpaired" msgstr "Ontkoppeld" #: src/service/ui/settings.js:333 msgid "Disconnected" msgstr "Niet verbonden" #: src/service/ui/settings.js:336 msgid "Connected" msgstr "Verbonden" #. TRANSLATORS: Description of where directly shared files are stored. #: src/service/ui/settings.js:406 #, javascript-format msgid "" "Transferred files are placed in the Downloads folder." msgstr "" "Overgedragen bestanden worden geplaatst in de map Downloads." #: src/service/ui/settings.js:499 msgid "A complete KDE Connect implementation for GNOME" msgstr "Een volledige KDE Connect-implementatie voor GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/service/ui/settings.js:508 msgid "translator-credits" msgstr "Heimen Stoffels" #: src/service/ui/settings.js:537 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 logbestand na." #: src/service/ui/settings.js:540 msgid "Review Log" msgstr "Logbestand nakijken" #. TRANSLATORS: When the battery level is 100% #: src/shell/device.js:82 msgid "Fully Charged" msgstr "Volledig opgeladen" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:86 #, 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:96 #, 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:104 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d:%02d resterend)" #: src/shell/donotdisturb.js:150 src/shell/donotdisturb.js:289 msgid "Do Not Disturb" msgstr "Niet storen" #: src/shell/donotdisturb.js:156 msgid "Silence Mobile Device Notifications" msgstr "Geen geluid bij mobiele meldingen" #: src/shell/donotdisturb.js:171 msgid "Until you turn off Do Not Disturb" msgstr "Totdat je Niet storen uitschakelt" #. TRANSLATORS: Time until change with time duration #. EXAMPLE: Until 10:00 (2 hours) #: src/shell/donotdisturb.js:184 src/shell/donotdisturb.js:278 #, javascript-format msgid "Until %s (%s)" msgstr "Tot %s (%s)" #: src/shell/donotdisturb.js:216 msgid "Done" msgstr "Klaar" #. TRANSLATORS: Time duration in hours (eg. 2 hours) #: src/shell/donotdisturb.js:263 #, javascript-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "%d uur" msgstr[1] "%d uur" #: src/shell/notification.js:42 msgid "Reply" msgstr "Beantwoorden" #. TRANSLATORS: Extension name #: webextension/gettext.js:27 msgid "GSConnect" msgstr "GSConnect" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:29 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:33 msgid "Service Unavailable" msgstr "Dienst niet beschikbaar" #. TRANSLATORS: No devices are known or available #: webextension/gettext.js:35 msgid "No Device Found" msgstr "Geen apparaat gevonden" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:37 msgid "Open in Browser" msgstr "Openen in browser" #~ msgid "KDE Connect" #~ msgstr "KDE Connect" #~ msgid "New Message" #~ msgstr "Nieuw bericht" #~ msgid "Debugger" #~ msgstr "Foutopsporing" #~ msgid "Appearance" #~ msgstr "Uiterlijk" #~ msgid "Service" #~ msgstr "Dienst" #~ 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 "Files Integration" #~ msgstr "Bestanden-integratie" #~ msgid "Additional Features" #~ msgstr "Extra functies" #~ msgid "Other" #~ msgstr "Overig" #~ 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" gnome-shell-extension-gsconnect-20/po/org.gnome.Shell.Extensions.GSConnect.pot000066400000000000000000000507061341554142200275760ustar00rootroot00000000000000# 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: 2018-12-29 16:44-0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: en\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: Open a dialog to connect to an IP or Bluez device #: data/connect.ui:24 data/menus.ui:7 msgid "Connect to…" msgstr "" #. Action Buttons #: data/connect.ui:30 data/notification.ui:14 data/telephony.ui:14 #: src/service/plugins/share.js:139 src/service/plugins/share.js:310 #: src/service/plugins/share.js:456 src/service/ui/device.js:607 #: src/service/ui/keybindings.js:44 src/service/ui/service.js:37 #: src/service/ui/settings.js:539 src/shell/donotdisturb.js:215 msgid "Cancel" msgstr "" #: data/connect.ui:37 msgid "Connect" msgstr "" #: data/connect.ui:98 msgid "IP Address" msgstr "" #: data/connect.ui:142 msgid "Bluetooth Device" msgstr "" #: data/contacts.ui:49 msgid "Type a phone number or name" msgstr "" #: data/contacts.ui:83 msgid "No contacts" msgstr "" #: data/contacts.ui:95 data/menus.ui:33 data/messaging.ui:247 msgid "Help" msgstr "" #: data/conversation.ui:76 data/conversation.ui:85 src/shell/notification.js:51 msgid "Type a message" msgstr "" #: data/conversation.ui:84 msgid "Send Message" msgstr "" #: data/device.ui:67 src/service/plugins/battery.js:12 msgid "Battery" msgstr "" #: data/device.ui:122 msgid "Clipboard Sync" msgstr "" #: data/device.ui:185 msgid "Media Players" msgstr "" #: data/device.ui:240 msgid "Mouse & Keyboard" msgstr "" #: data/device.ui:295 msgid "Volume Control" msgstr "" #: data/device.ui:345 data/device.ui:1728 msgid "Sharing" msgstr "" #: data/device.ui:374 data/device.ui:651 data/device.ui:1774 #: src/service/plugins/runcommand.js:18 src/service/plugins/runcommand.js:169 msgid "Commands" msgstr "" #: data/device.ui:424 data/device.ui:427 msgid "Name" msgstr "" #: data/device.ui:440 data/device.ui:446 msgid "Command Line" msgstr "" #: data/device.ui:444 data/device.ui:445 msgid "Choose an executable" msgstr "" #: data/device.ui:496 data/device.ui:511 msgid "Add" msgstr "" #: data/device.ui:527 data/device.ui:542 msgid "Remove" msgstr "" #: data/device.ui:576 data/device.ui:588 msgid "Edit" msgstr "" #: data/device.ui:604 data/device.ui:616 msgid "Save" msgstr "" #: data/device.ui:712 msgid "Share Notifications" msgstr "" #: data/device.ui:763 msgid "Applications" msgstr "" #: data/device.ui:809 data/device.ui:1820 #: src/service/plugins/notification.js:13 msgid "Notifications" msgstr "" #: data/device.ui:839 msgid "Incoming Calls" msgstr "" #: data/device.ui:888 data/device.ui:1055 msgid "Volume" msgstr "" #: data/device.ui:954 data/device.ui:1120 msgid "Pause Media" msgstr "" #: data/device.ui:1007 msgid "Ongoing Calls" msgstr "" #: data/device.ui:1176 msgid "Mute Microphone" msgstr "" #: data/device.ui:1230 data/device.ui:1866 src/service/plugins/telephony.js:13 msgid "Telephony" msgstr "" #: data/device.ui:1265 msgid "Action Shortcuts" msgstr "" #: data/device.ui:1280 data/device.ui:1349 msgid "Reset All…" msgstr "" #: data/device.ui:1334 msgid "Command Shortcuts" msgstr "" #: data/device.ui:1398 msgid "Shortcuts" msgstr "" #: data/device.ui:1429 msgid "Plugins" msgstr "" #: data/device.ui:1475 msgid "Experimental" msgstr "" #: data/device.ui:1524 msgid "Legacy SMS Support" msgstr "" #: data/device.ui:1598 msgid "Delete" msgstr "" #: data/device.ui:1627 msgid "Delete this device" msgstr "" #: data/device.ui:1645 msgid "Unpair and remove all settings and files" msgstr "" #: data/device.ui:1678 data/device.ui:1958 msgid "Advanced" msgstr "" #: data/device.ui:1912 msgid "Keyboard Shortcuts" msgstr "" #. TRANSLATORS: Send a pair request to the device #: data/device.ui:2015 data/menus.ui:68 src/service/device.js:424 msgid "Pair" msgstr "" #: data/device.ui:2047 msgid "Device is unpaired" msgstr "" #: data/device.ui:2062 msgid "You may configure this device before pairing" msgstr "" #: data/menus.ui:12 msgid "Display Mode" msgstr "" #. TRANSLATORS: Show device indicators in the top bar #: data/menus.ui:15 msgid "Panel" msgstr "" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/menus.ui:21 msgid "User Menu" msgstr "" #. TRANSLATORS: Generate a support log #: data/menus.ui:29 src/service/ui/settings.js:536 msgid "Generate Support Log" msgstr "" #: data/menus.ui:38 msgid "About" msgstr "" #. TRANSLATORS: Change the connection type to Bluetooth #: data/menus.ui:49 msgid "Switch to Bluetooth" msgstr "" #. TRANSLATORS: Change the connection type to TCP/IP #: data/menus.ui:56 msgid "Switch to LAN" msgstr "" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/menus.ui:63 src/service/ui/device.js:317 msgid "Encryption Info" msgstr "" #. TRANSLATORS: Unpair the device and notify it #: data/menus.ui:74 src/service/device.js:432 msgid "Unpair" msgstr "" #. TRANSLATORS: Send clipboard content to device #: data/menus.ui:86 msgid "To Device" msgstr "" #. TRANSLATORS: Receive clipboard content from the device #: data/menus.ui:92 msgid "From Device" msgstr "" #. TRANSLATORS: Don't change the system volume #: data/menus.ui:104 data/menus.ui:130 msgid "Nothing" msgstr "" #. TRANSLATORS: Lower the system volume #: data/menus.ui:111 data/menus.ui:137 msgid "Lower" msgstr "" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the phone ringer #: data/menus.ui:118 data/menus.ui:144 src/service/plugins/telephony.js:194 msgid "Mute" msgstr "" #: data/messaging.ui:12 src/service/plugins/sms.js:26 #: src/service/ui/messaging.js:911 msgid "Messaging" msgstr "" #: data/messaging.ui:21 src/service/ui/messaging.js:992 msgid "New Conversation" msgstr "" #: data/messaging.ui:110 msgid "No conversation selected" msgstr "" #: data/messaging.ui:126 msgid "Select or start a conversation" msgstr "" #: data/messaging.ui:193 data/notification.ui:52 data/telephony.ui:52 msgid "Device is disconnected" msgstr "" #: data/messaging.ui:264 msgid "No Conversations" msgstr "" #: data/notification.ui:21 data/telephony.ui:21 #: src/service/plugins/share.js:457 msgid "Send" msgstr "" #: data/settings.ui:10 msgid "Searching for devices…" msgstr "" #: data/settings.ui:49 data/settings.ui:63 msgid "Refresh" msgstr "" #: data/settings.ui:74 data/settings.ui:91 src/extension.js:105 msgid "Mobile Settings" msgstr "" #: data/settings.ui:144 data/settings.ui:158 msgid "Edit Device Name" msgstr "" #: data/settings.ui:236 msgid "Devices" msgstr "" #: data/settings.ui:299 msgid "Browser Add-Ons" msgstr "" #: data/settings.ui:579 msgid "Enable" msgstr "" #: data/settings.ui:611 msgid "This device is invisible to unpaired devices" msgstr "" #: data/settings.ui:623 src/service/daemon.js:532 msgid "Discovery Disabled" msgstr "" #. TRANSLATORS: Share URL by SMS #: data/telephony.ui:9 src/service/daemon.js:718 src/service/plugins/sms.js:50 #: webextension/gettext.js:39 msgid "Send SMS" msgstr "" #. Service Menu #: src/extension.js:79 src/extension.js:255 msgid "Mobile Devices" msgstr "" #. TRANSLATORS: %d is the number of devices connected #: src/extension.js:253 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "" msgstr[1] "" #. TRANSLATORS: Top-level context menu item for GSConnect #: src/nautilus-gsconnect.py:112 webextension/gettext.js:31 msgid "Send To Mobile Device" msgstr "" #: src/service/daemon.js:372 msgid "Report" msgstr "" #: src/service/daemon.js:507 msgid "Authentication Failure" msgstr "" #: src/service/daemon.js:516 msgid "Network Error" msgstr "" #: src/service/daemon.js:517 src/service/daemon.js:525 msgid "Click for help troubleshooting" msgstr "" #: src/service/daemon.js:524 msgid "PulseAudio Error" msgstr "" #: src/service/daemon.js:533 msgid "" "Discovery has been disabled due to the number of devices on this network." msgstr "" #: src/service/daemon.js:541 #, javascript-format msgid "%s Plugin Failed To Load" msgstr "" #: src/service/daemon.js:542 msgid "Click for more information" msgstr "" #: src/service/daemon.js:724 msgid "Dial Number" msgstr "" #: src/service/daemon.js:730 src/service/plugins/share.js:27 msgid "Share File" msgstr "" #: src/service/device.js:161 msgid "Not available" msgstr "" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:165 #, javascript-format msgid "Bluetooth device at %s" msgstr "" #. TRANSLATORS: Label for TLS Certificate fingerprint #. #. Example: #. #. Google Pixel Fingerprint: #. 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 #: src/service/device.js:183 src/service/device.js:185 #, javascript-format msgid "%s Fingerprint:" msgstr "" #: src/service/device.js:242 msgid "Laptop" msgstr "" #: src/service/device.js:244 msgid "Smartphone" msgstr "" #: src/service/device.js:246 msgid "Tablet" msgstr "" #: src/service/device.js:248 msgid "Desktop" msgstr "" #: src/service/device.js:408 src/service/ui/device.js:15 msgid "Reconnect" msgstr "" #: src/service/device.js:416 src/service/ui/device.js:16 #: src/service/ui/settings.js:371 msgid "Settings" msgstr "" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:607 #, javascript-format msgid "Pair Request from %s" msgstr "" #: src/service/device.js:614 msgid "Reject" msgstr "" #: src/service/device.js:619 msgid "Accept" msgstr "" #: src/service/plugins/battery.js:188 src/service/plugins/findmyphone.js:19 msgid "Ring" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:197 #, javascript-format msgid "%s: Battery is low" msgstr "" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:199 #, javascript-format msgid "%d%% remaining" msgstr "" #: src/service/plugins/clipboard.js:11 msgid "Clipboard" msgstr "" #: src/service/plugins/clipboard.js:17 msgid "Clipboard Push" msgstr "" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Pull" msgstr "" #: src/service/plugins/contacts.js:12 msgid "Contacts" msgstr "" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #: src/service/plugins/contacts.js:263 src/service/plugins/telephony.js:173 #: src/service/plugins/telephony.js:224 src/service/plugins/telephony.js:264 #: src/service/ui/contacts.js:156 src/service/ui/contacts.js:397 msgid "Unknown Contact" msgstr "" #: src/service/plugins/findmyphone.js:13 msgid "Find My Phone" msgstr "" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "" #: src/service/plugins/mousepad.js:28 src/service/plugins/mousepad.js:569 msgid "Keyboard" msgstr "" #: src/service/plugins/mousepad.js:223 msgid "Additional Software Required" msgstr "" #: src/service/plugins/mousepad.js:586 msgid "Keyboard not ready" msgstr "" #: src/service/plugins/mpris.js:10 msgid "MPRIS" msgstr "" #: src/service/plugins/notification.js:26 msgid "Cancel Notification" msgstr "" #: src/service/plugins/notification.js:34 msgid "Close Notification" msgstr "" #: src/service/plugins/notification.js:42 msgid "Reply Notification" msgstr "" #: src/service/plugins/notification.js:50 msgid "Send Notification" msgstr "" #: src/service/plugins/ping.js:11 src/service/plugins/ping.js:17 #: src/service/plugins/ping.js:46 msgid "Ping" msgstr "" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:53 #, javascript-format msgid "Ping: %s" msgstr "" #: src/service/plugins/runcommand.js:12 msgid "Run Commands" msgstr "" #: src/service/plugins/sftp.js:12 msgid "SFTP" msgstr "" #: src/service/plugins/sftp.js:18 msgid "Mount" msgstr "" #: src/service/plugins/sftp.js:26 msgid "Unmount" msgstr "" #: src/service/plugins/sftp.js:159 msgid "All files" msgstr "" #: src/service/plugins/sftp.js:160 msgid "Camera pictures" msgstr "" #: src/service/plugins/sftp.js:417 msgid "Files" msgstr "" #: src/service/plugins/share.js:13 src/service/plugins/share.js:19 msgid "Share" msgstr "" #: src/service/plugins/share.js:35 msgid "Share Text" msgstr "" #: src/service/plugins/share.js:43 src/service/ui/messaging.js:975 #: src/service/ui/messaging.js:983 msgid "Share Link" msgstr "" #: src/service/plugins/share.js:132 src/service/plugins/share.js:303 msgid "Starting Transfer" msgstr "" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:134 #, javascript-format msgid "Receiving “%s” from %s" msgstr "" #: src/service/plugins/share.js:172 src/service/plugins/share.js:327 msgid "Transfer Successful" msgstr "" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:174 #, javascript-format msgid "Received “%s” from %s" msgstr "" #: src/service/plugins/share.js:180 msgid "Open Folder" msgstr "" #: src/service/plugins/share.js:185 msgid "Open File" msgstr "" #: src/service/plugins/share.js:192 src/service/plugins/share.js:335 msgid "Transfer Failed" msgstr "" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:194 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "" #: src/service/plugins/share.js:233 #, javascript-format msgid "Text Shared By %s" msgstr "" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:305 #, javascript-format msgid "Sending “%s” to %s" msgstr "" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:329 #, javascript-format msgid "Sent “%s” to %s" msgstr "" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:337 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:408 #, javascript-format msgid "Send files to %s" msgstr "" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:412 msgid "Open when done" msgstr "" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:451 #, javascript-format msgid "Send a link to %s" msgstr "" #: src/service/plugins/sms.js:13 msgid "SMS" msgstr "" #: src/service/plugins/sms.js:34 msgid "New SMS (URI)" msgstr "" #: src/service/plugins/sms.js:42 src/service/plugins/telephony.js:22 msgid "Reply SMS" msgstr "" #: src/service/plugins/sms.js:58 msgid "Share SMS" msgstr "" #: src/service/plugins/systemvolume.js:11 msgid "System Volume" msgstr "" #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:190 msgid "Incoming call" msgstr "" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:205 msgid "Ongoing call" msgstr "" #. TRANSLATORS: All other phone number types #: src/service/ui/contacts.js:118 src/service/ui/contacts.js:139 #, javascript-format msgid "%s・Other" msgstr "" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:123 #, javascript-format msgid "%s・Fax" msgstr "" #. TRANSLATORS: A work phone number #: src/service/ui/contacts.js:127 #, javascript-format msgid "%s・Work" msgstr "" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:131 #, javascript-format msgid "%s・Mobile" msgstr "" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:135 #, javascript-format msgid "%s・Home" msgstr "" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #: src/service/ui/contacts.js:278 src/service/ui/contacts.js:292 #, javascript-format msgid "Send to %s" msgstr "" #: src/service/ui/device.js:608 msgid "Open" msgstr "" #: src/service/ui/device.js:660 src/service/ui/device.js:673 msgid "On" msgstr "" #: src/service/ui/device.js:660 src/service/ui/device.js:673 msgid "Off" msgstr "" #: src/service/ui/device.js:798 src/service/ui/device.js:826 #: src/service/ui/device.js:850 msgid "Disabled" msgstr "" #. TRANSLATORS: Title of keyboard shortcut dialog #: src/service/ui/keybindings.js:33 msgid "Set Shortcut" msgstr "" #. TRANSLATORS: Button to confirm the new shortcut #: src/service/ui/keybindings.js:47 msgid "Set" msgstr "" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/service/ui/keybindings.js:54 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "" #. TRANSLATORS: Keys for cancelling (␛) or resetting (␈) a shortcut #: src/service/ui/keybindings.js:83 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/service/ui/keybindings.js:182 #, javascript-format msgid "%s is already being used" msgstr "" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:65 src/service/ui/messaging.js:98 msgid "Just now" msgstr "" #. TRANSLATORS: Time duration in minutes (eg. 15 minutes) #: src/service/ui/messaging.js:70 src/service/ui/messaging.js:102 #: src/shell/donotdisturb.js:266 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "" msgstr[1] "" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:75 #, javascript-format msgid "Yesterday・%s" msgstr "" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:243 #, javascript-format msgid "You: %s" msgstr "" #: src/service/ui/service.js:31 msgid "Select a Device" msgstr "" #: src/service/ui/service.js:35 msgid "Select" msgstr "" #: src/service/ui/settings.js:331 msgid "Unpaired" msgstr "" #: src/service/ui/settings.js:333 msgid "Disconnected" msgstr "" #: src/service/ui/settings.js:336 msgid "Connected" msgstr "" #. TRANSLATORS: Description of where directly shared files are stored. #: src/service/ui/settings.js:406 #, javascript-format msgid "" "Transferred files are placed in the Downloads folder." msgstr "" #: src/service/ui/settings.js:499 msgid "A complete KDE Connect implementation for GNOME" msgstr "" #. TRANSLATORS: eg. 'Translator Name ' #: src/service/ui/settings.js:508 msgid "translator-credits" msgstr "" #: src/service/ui/settings.js:537 msgid "" "Debug messages are being logged. Take any steps necessary to reproduce a " "problem then review the log." msgstr "" #: src/service/ui/settings.js:540 msgid "Review Log" msgstr "" #. TRANSLATORS: When the battery level is 100% #: src/shell/device.js:82 msgid "Fully Charged" msgstr "" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:86 #, javascript-format msgid "%d%% (Estimating…)" msgstr "" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:96 #, 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:104 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "" #: src/shell/donotdisturb.js:150 src/shell/donotdisturb.js:289 msgid "Do Not Disturb" msgstr "" #: src/shell/donotdisturb.js:156 msgid "Silence Mobile Device Notifications" msgstr "" #: src/shell/donotdisturb.js:171 msgid "Until you turn off Do Not Disturb" msgstr "" #. TRANSLATORS: Time until change with time duration #. EXAMPLE: Until 10:00 (2 hours) #: src/shell/donotdisturb.js:184 src/shell/donotdisturb.js:278 #, javascript-format msgid "Until %s (%s)" msgstr "" #: src/shell/donotdisturb.js:216 msgid "Done" msgstr "" #. TRANSLATORS: Time duration in hours (eg. 2 hours) #: src/shell/donotdisturb.js:263 #, javascript-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "" msgstr[1] "" #: src/shell/notification.js:42 msgid "Reply" msgstr "" #. TRANSLATORS: Extension name #: webextension/gettext.js:27 msgid "GSConnect" msgstr "" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:29 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "" #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:33 msgid "Service Unavailable" msgstr "" #. TRANSLATORS: No devices are known or available #: webextension/gettext.js:35 msgid "No Device Found" msgstr "" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:37 msgid "Open in Browser" msgstr "" gnome-shell-extension-gsconnect-20/po/pl.po000066400000000000000000000615541341554142200211070ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-12-29 16:44-0800\n" "PO-Revision-Date: 2018-12-30 01:32\n" "Last-Translator: andyholmes \n" "Language-Team: Polish\n" "Language: 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-Generator: crowdin.com\n" "X-Crowdin-Project: gsconnect\n" "X-Crowdin-Language: pl\n" "X-Crowdin-File: /master/po/org.gnome.Shell.Extensions.GSConnect.pot\n" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/connect.ui:24 data/menus.ui:7 msgid "Connect to…" msgstr "Połącz z…" #. Action Buttons #: data/connect.ui:30 data/notification.ui:14 data/telephony.ui:14 #: src/service/plugins/share.js:139 src/service/plugins/share.js:310 #: src/service/plugins/share.js:456 src/service/ui/device.js:607 #: src/service/ui/keybindings.js:44 src/service/ui/service.js:37 #: src/service/ui/settings.js:539 src/shell/donotdisturb.js:215 msgid "Cancel" msgstr "Anuluj" #: data/connect.ui:37 msgid "Connect" msgstr "Połącz" #: data/connect.ui:98 msgid "IP Address" msgstr "Adres IP" #: data/connect.ui:142 msgid "Bluetooth Device" msgstr "Urządzenie Bluetooth" #: data/contacts.ui:49 msgid "Type a phone number or name" msgstr "Numer telefonu lub nazwa kontaktu" #: data/contacts.ui:83 msgid "No contacts" msgstr "Brak kontaktów" #: data/contacts.ui:95 data/menus.ui:33 data/messaging.ui:247 msgid "Help" msgstr "Pomoc" #: data/conversation.ui:76 data/conversation.ui:85 src/shell/notification.js:51 msgid "Type a message" msgstr "Napisz wiadomość" #: data/conversation.ui:84 msgid "Send Message" msgstr "Wysyła wiadomość" #: data/device.ui:67 src/service/plugins/battery.js:12 msgid "Battery" msgstr "Akumulator" #: data/device.ui:122 msgid "Clipboard Sync" msgstr "Synchronizacja schowka" #: data/device.ui:185 msgid "Media Players" msgstr "Odtwarzacze multimediów" #: data/device.ui:240 msgid "Mouse & Keyboard" msgstr "Mysz i klawiatura" #: data/device.ui:295 msgid "Volume Control" msgstr "Sterowanie głośnością" #: data/device.ui:345 data/device.ui:1728 msgid "Sharing" msgstr "Udostępnianie" #: data/device.ui:374 data/device.ui:651 data/device.ui:1774 #: src/service/plugins/runcommand.js:18 src/service/plugins/runcommand.js:169 msgid "Commands" msgstr "Polecenia" #: data/device.ui:424 data/device.ui:427 msgid "Name" msgstr "Nazwa" #: data/device.ui:440 data/device.ui:446 msgid "Command Line" msgstr "Wiersz poleceń" #: data/device.ui:444 data/device.ui:445 msgid "Choose an executable" msgstr "Wybiera plik wykonywalny" #: data/device.ui:496 data/device.ui:511 msgid "Add" msgstr "Dodaje" #: data/device.ui:527 data/device.ui:542 msgid "Remove" msgstr "Usuwa" #: data/device.ui:576 data/device.ui:588 msgid "Edit" msgstr "Modyfikuje" #: data/device.ui:604 data/device.ui:616 msgid "Save" msgstr "Zapisuje" #: data/device.ui:712 msgid "Share Notifications" msgstr "Udostępnianie powiadomień" #: data/device.ui:763 msgid "Applications" msgstr "Programy" #: data/device.ui:809 data/device.ui:1820 #: src/service/plugins/notification.js:13 msgid "Notifications" msgstr "Powiadomienia" #: data/device.ui:839 msgid "Incoming Calls" msgstr "Połączenia przychodzące" #: data/device.ui:888 data/device.ui:1055 msgid "Volume" msgstr "Głośność" #: data/device.ui:954 data/device.ui:1120 msgid "Pause Media" msgstr "Wstrzymywanie multimediów" #: data/device.ui:1007 msgid "Ongoing Calls" msgstr "Trwające połączenia" #: data/device.ui:1176 msgid "Mute Microphone" msgstr "Wyciszanie mikrofonu" #: data/device.ui:1230 data/device.ui:1866 src/service/plugins/telephony.js:13 msgid "Telephony" msgstr "Komunikacja" #: data/device.ui:1265 msgid "Action Shortcuts" msgstr "Skróty działań" #: data/device.ui:1280 data/device.ui:1349 msgid "Reset All…" msgstr "Przywróć wszystko…" #: data/device.ui:1334 msgid "Command Shortcuts" msgstr "Skróty poleceń" #: data/device.ui:1398 msgid "Shortcuts" msgstr "Skróty" #: data/device.ui:1429 msgid "Plugins" msgstr "Wtyczki" #: data/device.ui:1475 msgid "Experimental" msgstr "Eksperymentalne" #: data/device.ui:1524 msgid "Legacy SMS Support" msgstr "Obsługa SMS (poprzednia wersja)" #: data/device.ui:1598 msgid "Delete" msgstr "Usuń" #: data/device.ui:1627 msgid "Delete this device" msgstr "Usuń to urządzenie" #: data/device.ui:1645 msgid "Unpair and remove all settings and files" msgstr "Odwiąż i usuń wszystkie ustawienia i pliki" #: data/device.ui:1678 data/device.ui:1958 msgid "Advanced" msgstr "Zaawansowane" #: data/device.ui:1912 msgid "Keyboard Shortcuts" msgstr "Skróty klawiszowe" #. TRANSLATORS: Send a pair request to the device #: data/device.ui:2015 data/menus.ui:68 src/service/device.js:424 msgid "Pair" msgstr "Powiąż" #: data/device.ui:2047 msgid "Device is unpaired" msgstr "Urządzenie jest niepowiązane" #: data/device.ui:2062 msgid "You may configure this device before pairing" msgstr "Można skonfigurować to urządzenie przed powiązaniem" #: data/menus.ui:12 msgid "Display Mode" msgstr "Tryb wyświetlania" #. TRANSLATORS: Show device indicators in the top bar #: data/menus.ui:15 msgid "Panel" msgstr "Panel" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/menus.ui:21 msgid "User Menu" msgstr "Menu użytkownika" #. TRANSLATORS: Generate a support log #: data/menus.ui:29 src/service/ui/settings.js:536 msgid "Generate Support Log" msgstr "Utwórz dziennik wsparcia" #: data/menus.ui:38 msgid "About" msgstr "O programie" #. TRANSLATORS: Change the connection type to Bluetooth #: data/menus.ui:49 msgid "Switch to Bluetooth" msgstr "Przełącz na sieć Bluetooth" #. TRANSLATORS: Change the connection type to TCP/IP #: data/menus.ui:56 msgid "Switch to LAN" msgstr "Przełącz na sieć LAN" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/menus.ui:63 src/service/ui/device.js:317 msgid "Encryption Info" msgstr "Informacje o szyfrowaniu" #. TRANSLATORS: Unpair the device and notify it #: data/menus.ui:74 src/service/device.js:432 msgid "Unpair" msgstr "Odwiąż" #. TRANSLATORS: Send clipboard content to device #: data/menus.ui:86 msgid "To Device" msgstr "Do urządzenia" #. TRANSLATORS: Receive clipboard content from the device #: data/menus.ui:92 msgid "From Device" msgstr "Z urządzenia" #. TRANSLATORS: Don't change the system volume #: data/menus.ui:104 data/menus.ui:130 msgid "Nothing" msgstr "Nic" #. TRANSLATORS: Lower the system volume #: data/menus.ui:111 data/menus.ui:137 msgid "Lower" msgstr "Ciszej" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the phone ringer #: data/menus.ui:118 data/menus.ui:144 src/service/plugins/telephony.js:194 msgid "Mute" msgstr "Wycisz" #: data/messaging.ui:12 src/service/plugins/sms.js:26 #: src/service/ui/messaging.js:911 msgid "Messaging" msgstr "Wiadomości" #: data/messaging.ui:21 src/service/ui/messaging.js:992 msgid "New Conversation" msgstr "Nowa rozmowa" #: data/messaging.ui:110 msgid "No conversation selected" msgstr "Nie wybrano rozmowy" #: data/messaging.ui:126 msgid "Select or start a conversation" msgstr "Wybierz lub rozpocznij rozmowę" #: data/messaging.ui:193 data/notification.ui:52 data/telephony.ui:52 msgid "Device is disconnected" msgstr "Urządzenie jest rozłączone" #: data/messaging.ui:264 msgid "No Conversations" msgstr "Brak rozmów" #: data/notification.ui:21 data/telephony.ui:21 #: src/service/plugins/share.js:457 msgid "Send" msgstr "Wyślij" #: data/settings.ui:10 msgid "Searching for devices…" msgstr "Wyszukiwanie urządzeń…" #: data/settings.ui:49 data/settings.ui:63 msgid "Refresh" msgstr "Odśwież" #: data/settings.ui:74 data/settings.ui:91 src/extension.js:105 msgid "Mobile Settings" msgstr "Ustawienia urządzeń mobilnych" #: data/settings.ui:144 data/settings.ui:158 msgid "Edit Device Name" msgstr "Modyfikuje nazwę urządzenia" #: data/settings.ui:236 msgid "Devices" msgstr "Urządzenia" #: data/settings.ui:299 msgid "Browser Add-Ons" msgstr "Dodatki do przeglądarek" #: data/settings.ui:579 msgid "Enable" msgstr "Włącz" #: data/settings.ui:611 msgid "This device is invisible to unpaired devices" msgstr "To urządzenie jest niewidoczne dla niepowiązanych urządzeń" #: data/settings.ui:623 src/service/daemon.js:532 msgid "Discovery Disabled" msgstr "Wykrywanie jest wyłączone" #. TRANSLATORS: Share URL by SMS #: data/telephony.ui:9 src/service/daemon.js:718 src/service/plugins/sms.js:50 #: webextension/gettext.js:39 msgid "Send SMS" msgstr "Wyślij SMS" #. Service Menu #: src/extension.js:79 src/extension.js:255 msgid "Mobile Devices" msgstr "Urządzenia mobilne" #. TRANSLATORS: %d is the number of devices connected #: src/extension.js:253 #, 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ń" #. TRANSLATORS: Top-level context menu item for GSConnect #: src/nautilus-gsconnect.py:112 webextension/gettext.js:31 msgid "Send To Mobile Device" msgstr "Wyślij na urządzenie mobilne" #: src/service/daemon.js:372 msgid "Report" msgstr "Zgłoś" #: src/service/daemon.js:507 msgid "Authentication Failure" msgstr "Niepowodzenie uwierzytelnienia" #: src/service/daemon.js:516 msgid "Network Error" msgstr "Błąd sieci" #: src/service/daemon.js:517 src/service/daemon.js:525 msgid "Click for help troubleshooting" msgstr "Uzyskaj pomoc w rozwiązaniu problemu" #: src/service/daemon.js:524 msgid "PulseAudio Error" msgstr "Błąd usługi PulseAudio" #: src/service/daemon.js:533 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/daemon.js:541 #, javascript-format msgid "%s Plugin Failed To Load" msgstr "Wczytanie wtyczki %s się nie powiodło" #: src/service/daemon.js:542 msgid "Click for more information" msgstr "Więcej informacji" #: src/service/daemon.js:724 msgid "Dial Number" msgstr "Zadzwoń" #: src/service/daemon.js:730 src/service/plugins/share.js:27 msgid "Share File" msgstr "Udostępnij plik" #: src/service/device.js:161 msgid "Not available" msgstr "Niedostępne" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:165 #, javascript-format msgid "Bluetooth device at %s" msgstr "Urządzenie Bluetooth pod adresem %s" #. TRANSLATORS: Label for TLS Certificate fingerprint #. #. Example: #. #. Google Pixel Fingerprint: #. 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 #: src/service/device.js:183 src/service/device.js:185 #, javascript-format msgid "%s Fingerprint:" msgstr "Odcisk urządzenia %s:" #: src/service/device.js:242 msgid "Laptop" msgstr "Laptop" #: src/service/device.js:244 msgid "Smartphone" msgstr "Smartfon" #: src/service/device.js:246 msgid "Tablet" msgstr "Tablet" #: src/service/device.js:248 msgid "Desktop" msgstr "Komputer stacjonarny" #: src/service/device.js:408 src/service/ui/device.js:15 msgid "Reconnect" msgstr "Połącz ponownie" #: src/service/device.js:416 src/service/ui/device.js:16 #: src/service/ui/settings.js:371 msgid "Settings" msgstr "Ustawienia" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:607 #, javascript-format msgid "Pair Request from %s" msgstr "Prośba o powiązanie z urządzenia %s" #: src/service/device.js:614 msgid "Reject" msgstr "Odrzuć" #: src/service/device.js:619 msgid "Accept" msgstr "Przyjmij" #: src/service/plugins/battery.js:188 src/service/plugins/findmyphone.js:19 msgid "Ring" msgstr "Zadzwoń" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:197 #, javascript-format msgid "%s: Battery is low" msgstr "%s: poziom naładowania akumulatora jest niski" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:199 #, javascript-format msgid "%d%% remaining" msgstr "Pozostało %d%%" #: src/service/plugins/clipboard.js:11 msgid "Clipboard" msgstr "Schowek" #: src/service/plugins/clipboard.js:17 msgid "Clipboard Push" msgstr "Wysyłanie do schowka" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Pull" msgstr "Odbieranie ze schowka" #: src/service/plugins/contacts.js:12 msgid "Contacts" msgstr "Kontakty" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #: src/service/plugins/contacts.js:263 src/service/plugins/telephony.js:173 #: src/service/plugins/telephony.js:224 src/service/plugins/telephony.js:264 #: src/service/ui/contacts.js:156 src/service/ui/contacts.js:397 msgid "Unknown Contact" msgstr "Nieznany kontakt" #: src/service/plugins/findmyphone.js:13 msgid "Find My Phone" msgstr "Znajdź mój telefon" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Podkładka pod mysz" #: src/service/plugins/mousepad.js:28 src/service/plugins/mousepad.js:569 msgid "Keyboard" msgstr "Klawiatura" #: src/service/plugins/mousepad.js:223 msgid "Additional Software Required" msgstr "Wymagane jest dodatkowe oprogramowanie" #: src/service/plugins/mousepad.js:586 msgid "Keyboard not ready" msgstr "Klawiatura nie jest gotowa" #: src/service/plugins/mpris.js:10 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/notification.js:26 msgid "Cancel Notification" msgstr "Anuluj powiadomienie" #: src/service/plugins/notification.js:34 msgid "Close Notification" msgstr "Zamknij powiadomienie" #: src/service/plugins/notification.js:42 msgid "Reply Notification" msgstr "Odpowiedz na powiadomienie" #: src/service/plugins/notification.js:50 msgid "Send Notification" msgstr "Wyślij powiadomienie" #: src/service/plugins/ping.js:11 src/service/plugins/ping.js:17 #: src/service/plugins/ping.js:46 msgid "Ping" 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:53 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/runcommand.js:12 msgid "Run Commands" msgstr "Wykonywanie poleceń" #: src/service/plugins/sftp.js:12 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:18 msgid "Mount" msgstr "Zamontuj" #: src/service/plugins/sftp.js:26 msgid "Unmount" msgstr "Odmontuj" #: src/service/plugins/sftp.js:159 msgid "All files" msgstr "Wszystkie pliki" #: src/service/plugins/sftp.js:160 msgid "Camera pictures" msgstr "Zdjęcia z aparatu" #: src/service/plugins/sftp.js:417 msgid "Files" msgstr "Pliki" #: src/service/plugins/share.js:13 src/service/plugins/share.js:19 msgid "Share" msgstr "Udostępnij" #: src/service/plugins/share.js:35 msgid "Share Text" msgstr "Udostępnij tekst" #: src/service/plugins/share.js:43 src/service/ui/messaging.js:975 #: src/service/ui/messaging.js:983 msgid "Share Link" msgstr "Udostępnij odnośnik" #: src/service/plugins/share.js:132 src/service/plugins/share.js:303 msgid "Starting Transfer" msgstr "Rozpoczynanie przesyłania" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:134 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Odbieranie „%s” z urządzenia %s" #: src/service/plugins/share.js:172 src/service/plugins/share.js:327 msgid "Transfer Successful" msgstr "Pomyślnie przesłano" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:174 #, javascript-format msgid "Received “%s” from %s" msgstr "Odebrano „%s” z urządzenia %s" #: src/service/plugins/share.js:180 msgid "Open Folder" msgstr "Otwórz katalog" #: src/service/plugins/share.js:185 msgid "Open File" msgstr "Otwórz plik" #: src/service/plugins/share.js:192 src/service/plugins/share.js:335 msgid "Transfer Failed" msgstr "Przesłanie się nie powiodło" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:194 #, 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:233 #, 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:305 #, 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:329 #, 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:337 #, 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:408 #, 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:412 msgid "Open when done" msgstr "Otwarcie po ukończeniu" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:451 #, javascript-format msgid "Send a link to %s" msgstr "Wysyła odnośnik do urządzenia %s" #: src/service/plugins/sms.js:13 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:34 msgid "New SMS (URI)" msgstr "Nowy SMS (adres URI)" #: src/service/plugins/sms.js:42 src/service/plugins/telephony.js:22 msgid "Reply SMS" msgstr "Odpowiedz na SMS" #: src/service/plugins/sms.js:58 msgid "Share SMS" msgstr "Udostępnij SMS" #: src/service/plugins/systemvolume.js:11 msgid "System Volume" msgstr "Głośność systemu" #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Wycisz połączenie" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:190 msgid "Incoming call" msgstr "Połączenie przychodzące" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:205 msgid "Ongoing call" msgstr "Trwające połączenie" #. TRANSLATORS: All other phone number types #: src/service/ui/contacts.js:118 src/service/ui/contacts.js:139 #, javascript-format msgid "%s・Other" msgstr "%s・Inne" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:123 #, javascript-format msgid "%s・Fax" msgstr "%s・Faks" #. TRANSLATORS: A work phone number #: src/service/ui/contacts.js:127 #, javascript-format msgid "%s・Work" msgstr "%s・Praca" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:131 #, javascript-format msgid "%s・Mobile" msgstr "%s・Komórkowy" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:135 #, javascript-format msgid "%s・Home" msgstr "%s・Domowy" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #: src/service/ui/contacts.js:278 src/service/ui/contacts.js:292 #, javascript-format msgid "Send to %s" msgstr "Wyślij do „%s”" #: src/service/ui/device.js:608 msgid "Open" msgstr "Otwórz" #: src/service/ui/device.js:660 src/service/ui/device.js:673 msgid "On" msgstr "Włączone" #: src/service/ui/device.js:660 src/service/ui/device.js:673 msgid "Off" msgstr "Wyłączone" #: src/service/ui/device.js:798 src/service/ui/device.js:826 #: src/service/ui/device.js:850 msgid "Disabled" msgstr "Wyłączone" #. TRANSLATORS: Title of keyboard shortcut dialog #: src/service/ui/keybindings.js:33 msgid "Set Shortcut" msgstr "Ustawienie skrótu" #. TRANSLATORS: Button to confirm the new shortcut #: src/service/ui/keybindings.js:47 msgid "Set" msgstr "Ustaw" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/service/ui/keybindings.js:54 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Proszę wprowadzić nowy skrót, aby zmienić „%s”" #. TRANSLATORS: Keys for cancelling (␛) or resetting (␈) a shortcut #: src/service/ui/keybindings.js:83 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "Klawisz Esc anuluje, a Backspace przywróci skrót klawiszowy." #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/service/ui/keybindings.js:182 #, javascript-format msgid "%s is already being used" msgstr "%s jest już używane" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:65 src/service/ui/messaging.js:98 msgid "Just now" msgstr "Przed chwilą" #. TRANSLATORS: Time duration in minutes (eg. 15 minutes) #: src/service/ui/messaging.js:70 src/service/ui/messaging.js:102 #: src/shell/donotdisturb.js:266 #, 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" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:75 #, javascript-format msgid "Yesterday・%s" msgstr "Wczoraj・%s" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:243 #, javascript-format msgid "You: %s" msgstr "Ja: %s" #: src/service/ui/service.js:31 msgid "Select a Device" msgstr "Wybór urządzenia" #: src/service/ui/service.js:35 msgid "Select" msgstr "Wybierz" #: src/service/ui/settings.js:331 msgid "Unpaired" msgstr "Niepowiązane" #: src/service/ui/settings.js:333 msgid "Disconnected" msgstr "Rozłączone" #: src/service/ui/settings.js:336 msgid "Connected" msgstr "Połączone" #. TRANSLATORS: Description of where directly shared files are stored. #: src/service/ui/settings.js:406 #, javascript-format msgid "Transferred files are placed in the Downloads folder." msgstr "Przesłane pliki są umieszczane w katalogu Pobrane." #: src/service/ui/settings.js:499 msgid "A complete KDE Connect implementation for GNOME" msgstr "Pełna implementacja KDE Connect dla środowiska GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/service/ui/settings.js:508 msgid "translator-credits" msgstr "Adrian Kryński , 2017\n" "Piotr Drąg , 2018\n" "Aviary.pl , 2018" #: src/service/ui/settings.js:537 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/service/ui/settings.js:540 msgid "Review Log" msgstr "Przejrzyj dziennik" #. TRANSLATORS: When the battery level is 100% #: src/shell/device.js:82 msgid "Fully Charged" msgstr "W pełni naładowane" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:86 #, 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:96 #, 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:104 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (pozostało: %d∶%02d)" #: src/shell/donotdisturb.js:150 src/shell/donotdisturb.js:289 msgid "Do Not Disturb" msgstr "Nie przeszkadzać" #: src/shell/donotdisturb.js:156 msgid "Silence Mobile Device Notifications" msgstr "Wycisz powiadomienia z urządzenia mobilnego" #: src/shell/donotdisturb.js:171 msgid "Until you turn off Do Not Disturb" msgstr "Do wyłączenia trybu „Nie przeszkadzać”" #. TRANSLATORS: Time until change with time duration #. EXAMPLE: Until 10:00 (2 hours) #: src/shell/donotdisturb.js:184 src/shell/donotdisturb.js:278 #, javascript-format msgid "Until %s (%s)" msgstr "Do %s (%s)" #: src/shell/donotdisturb.js:216 msgid "Done" msgstr "Gotowe" #. TRANSLATORS: Time duration in hours (eg. 2 hours) #: src/shell/donotdisturb.js:263 #, javascript-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "%d godzina" msgstr[1] "%d godziny" msgstr[2] "%d godzin" msgstr[3] "%d godzin" #: src/shell/notification.js:42 msgid "Reply" msgstr "Odpowiedz" #. TRANSLATORS: Extension name #: webextension/gettext.js:27 msgid "GSConnect" msgstr "GSConnect" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:29 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:33 msgid "Service Unavailable" msgstr "Usługa jest niedostępna" #. TRANSLATORS: No devices are known or available #: webextension/gettext.js:35 msgid "No Device Found" msgstr "Nie odnaleziono żadnego urządzenia" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:37 msgid "Open in Browser" msgstr "Otwórz w przeglądarce" gnome-shell-extension-gsconnect-20/po/pt_BR.po000066400000000000000000000552321341554142200214760ustar00rootroot00000000000000# Brazilian Portuguese translation for GSConnect. # Copyright (C) 2018 Andy Holmes # This file is distributed under the same license as the org.gnome.Shell.Extensions.GSConnect package. # Ricardo Silva Veloso , 2018. # msgid "" msgstr "" "Project-Id-Version: org.gnome.Shell.Extensions.GSConnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-11-19 11:14-0800\n" "PO-Revision-Date: 2018-11-24 21:11-0200\n" "Last-Translator: Ricardo Silva Veloso \n" "Language-Team: \n" "Language: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" "X-Generator: Poedit 2.2\n" #: data/conversation.ui:71 data/conversation.ui:79 msgid "Type a message" msgstr "Escrever uma mensagem" #: data/contacts.ui:51 data/contacts.ui:52 msgid "Type a phone number or name" msgstr "Digite um número de telefone ou nome" #: data/device.ui:68 src/service/plugins/battery.js:12 msgid "Battery" msgstr "Bateria" #: data/device.ui:116 msgid "Clipboard Sync" msgstr "Sincronização da área de transferência" #: data/device.ui:172 msgid "Media Players" msgstr "Reprodutores de mídia" #: data/device.ui:220 msgid "Mouse & Keyboard" msgstr "Mouse & teclado" #: data/device.ui:268 msgid "Volume Control" msgstr "Controle de volume" #: data/device.ui:308 data/device.ui:1406 msgid "Sharing" msgstr "Compartilhar" #: data/device.ui:337 data/device.ui:534 data/device.ui:1449 #: src/service/plugins/runcommand.js:18 src/service/plugins/runcommand.js:169 msgid "Commands" msgstr "Comandos" #: data/device.ui:385 msgid "Name" msgstr "Nome" #: data/device.ui:401 data/device.ui:402 msgid "Choose an executable" msgstr "Escolher um executável" #: data/device.ui:403 msgid "Command Line" msgstr "Linha de comando" #: data/device.ui:597 msgid "Share Notifications" msgstr "Compartilhar notificações" #: data/device.ui:636 msgid "Applications" msgstr "Aplicativos" #: data/device.ui:675 data/device.ui:1492 #: src/service/plugins/notification.js:12 msgid "Notifications" msgstr "Notificações" #: data/device.ui:705 msgid "Incoming Calls" msgstr "Chamadas recebidas" #: data/device.ui:755 data/device.ui:900 msgid "Volume" msgstr "Volume" #: data/device.ui:813 data/device.ui:958 msgid "Pause Media" msgstr "Pausar mídia" #: data/device.ui:852 msgid "Ongoing Calls" msgstr "Chamadas em andamento" #: data/device.ui:1007 msgid "Mute Microphone" msgstr "Silenciar microfone" #: data/device.ui:1047 data/device.ui:1535 src/service/plugins/telephony.js:12 msgid "Telephony" msgstr "Telefonia" #: data/device.ui:1082 msgid "Action Shortcuts" msgstr "Atalhos de ações" #: data/device.ui:1094 data/device.ui:1157 msgid "Reset All…" msgstr "Redefinir tudo…" #: data/device.ui:1145 msgid "Command Shortcuts" msgstr "Atalhos de comandos" #: data/device.ui:1203 msgid "Shortcuts" msgstr "Atalhos" #: data/device.ui:1234 msgid "Plugins" msgstr "Plugins" #: data/device.ui:1295 msgid "Delete" msgstr "Excluir" #: data/device.ui:1320 msgid "Delete this device" msgstr "Excluir este dispositivo" #: data/device.ui:1335 msgid "Unpair and remove all settings and files" msgstr "Esquecer e remover todas as configurações e arquivos" #: data/device.ui:1365 data/device.ui:1621 msgid "Advanced" msgstr "Avançado" #: data/device.ui:1578 msgid "Keyboard Shortcuts" msgstr "Atalhos de teclado" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/menus.ui:7 src/service/ui/service.js:115 msgid "Connect to…" msgstr "Conectar a…" #: data/menus.ui:13 data/settings.ui:881 msgid "Help" msgstr "Ajuda" #. TRANSLATORS: Open the developer's dialog #: data/menus.ui:19 msgid "Debugger" msgstr "Depurador" #: data/menus.ui:23 msgid "About" msgstr "Sobre" #. TRANSLATORS: Change the connection type to Bluetooth #: data/menus.ui:34 msgid "Switch to Bluetooth" msgstr "Mudar para Bluetooth" #. TRANSLATORS: Change the connection type to TCP/IP #: data/menus.ui:41 msgid "Switch to LAN" msgstr "Mudar para LAN" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/menus.ui:48 src/service/ui/settings.js:612 msgid "Encryption Info" msgstr "Informação de criptografia" #. TRANSLATORS: Send a pair request to the device #: data/menus.ui:53 src/service/device.js:425 msgid "Pair" msgstr "Parear" #. TRANSLATORS: Unpair the device and notify it #: data/menus.ui:59 src/service/device.js:433 msgid "Unpair" msgstr "Esquecer" #. TRANSLATORS: Send clipboard content to device #: data/menus.ui:71 msgid "To Device" msgstr "Para o dispositivo" #. TRANSLATORS: Receive clipboard content from the device #: data/menus.ui:77 msgid "From Device" msgstr "Do dispositivo" #. TRANSLATORS: Don't change the system volume #: data/menus.ui:89 data/menus.ui:115 msgid "Nothing" msgstr "Nada" #. TRANSLATORS: Lower the system volume #: data/menus.ui:96 data/menus.ui:122 msgid "Lower" msgstr "Baixo" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the phone ringer #: data/menus.ui:103 data/menus.ui:129 src/service/plugins/telephony.js:176 msgid "Mute" msgstr "Silenciar" #: data/messaging.ui:12 src/service/plugins/sms.js:25 #: src/service/ui/messaging.js:871 msgid "Messaging" msgstr "Mensagens" #: data/messaging.ui:110 msgid "Select a conversation" msgstr "Selecione uma conversa" #: data/messaging.ui:188 msgid "Device is disconnected" msgstr "Dispositivo está desconectado" #: data/settings.ui:333 msgid "Appearance" msgstr "Aparência" #: data/settings.ui:383 msgid "Display Mode" msgstr "Modo de exibição" #: data/settings.ui:425 msgid "Service" msgstr "Serviço" #: data/settings.ui:475 msgid "Discoverable" msgstr "Visível" #: data/settings.ui:528 msgid "Restart Service" msgstr "Reiniciar serviço" #: data/settings.ui:581 data/settings.ui:1015 src/service/device.js:417 msgid "Settings" msgstr "Configurações" #: data/settings.ui:639 msgid "Remote Filesystems" msgstr "Sistemas de arquivos remotos" #: data/settings.ui:675 msgid "Sound Effects" msgstr "Efeitos sonoros" #: data/settings.ui:712 msgid "Extended Keyboard Support" msgstr "Suporte a teclado estendido" #: data/settings.ui:749 msgid "Desktop Contacts" msgstr "Integração com contatos" #: data/settings.ui:786 msgid "Files Integration" msgstr "Integração com arquivos" #: data/settings.ui:813 msgid "Additional Features" msgstr "Funcionalidades adicionais" #: data/settings.ui:903 msgid "Browser Add-Ons" msgstr "Complementos para navegadores" #: data/settings.ui:921 msgid "KDE Connect" msgstr "KDE Connect" #: data/settings.ui:956 data/settings.ui:1058 msgid "Other" msgstr "Outro" #. TRANSLATORS: No devices are known or available #: data/settings.ui:1106 webextension/gettext.js:35 msgid "No Device Found" msgstr "Nenhum dispositivo encontrado" #: data/settings.ui:1142 msgid "Refresh" msgstr "Recarregar" #. Service Menu #: src/extension.js:79 src/extension.js:259 msgid "Mobile Devices" msgstr "Dispositivos móveis" #: src/extension.js:105 msgid "Mobile Settings" msgstr "Configurações de dispositivos" #. TRANSLATORS: Extension name #: src/extension.js:196 src/extension.js:311 src/service/daemon.js:95 #: src/service/daemon.js:413 webextension/gettext.js:27 msgid "GSConnect" msgstr "GSConnect" #. TRANSLATORS: %d is the number of devices connected #: src/extension.js:257 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d conectado" msgstr[1] "%d conectados" #. TRANSLATORS: Top-level context menu item for GSConnect #: src/nautilus-gsconnect.py:112 webextension/gettext.js:31 msgid "Send To Mobile Device" msgstr "Enviar para dispositivo móvel" #: src/service/daemon.js:406 msgid "A complete KDE Connect implementation for GNOME" msgstr "Uma implementação completa do KDE Connect para o GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/service/daemon.js:415 msgid "translator-credits" msgstr "Ricardo Silva Veloso " #: src/service/daemon.js:437 msgid "Report" msgstr "Reportar" #: src/service/daemon.js:567 msgid "Authentication Failure" msgstr "Falha de autenticação" #: src/service/daemon.js:576 msgid "Network Error" msgstr "Erro de rede" #: src/service/daemon.js:577 src/service/daemon.js:587 msgid "Click for help troubleshooting" msgstr "Clique para ajuda na solução de problemas" #: src/service/daemon.js:586 msgid "PulseAudio Error" msgstr "Erro do PulseAudio" #: src/service/daemon.js:596 msgid "Discovery Disabled" msgstr "Descoberta desativada" #: src/service/daemon.js:597 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/daemon.js:599 src/service/daemon.js:609 msgid "Click to open preferences" msgstr "Clique para abrir preferências" #: src/service/daemon.js:608 msgid "Additional Software Required" msgstr "Necessário software adicional" #: src/service/daemon.js:617 #, javascript-format msgid "%s Plugin Failed To Load" msgstr "Falha ao carregar o plugin %s" #: src/service/daemon.js:618 src/service/daemon.js:635 msgid "Click for more information" msgstr "Clique para mais informação" #: src/service/daemon.js:633 msgid "Wayland Not Supported" msgstr "Sem suporte a Wayland" #: src/service/daemon.js:634 msgid "Remote input not supported on Wayland" msgstr "Não há suporte para entrada remota no Wayland" #. Create an urgent notification #: src/service/daemon.js:658 #, javascript-format msgid "GSConnect: %s" msgstr "GSConnect: %s" #. TRANSLATORS: Share URL by SMS #: src/service/daemon.js:808 src/service/plugins/sms.js:49 #: webextension/gettext.js:39 msgid "Send SMS" msgstr "Enviar SMS" #: src/service/daemon.js:812 msgid "Dial Number" msgstr "Ligar para número" #: src/service/device.js:166 msgid "Not available" msgstr "Indisponível" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:170 #, javascript-format msgid "Bluetooth device at %s" msgstr "Dispositivo Bluetooth em %s" #. TRANSLATORS: Label for TLS Certificate fingerprint #. #. Example: #. #. Google Pixel Fingerprint: #. 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 #: src/service/device.js:188 src/service/device.js:190 #, javascript-format msgid "%s Fingerprint:" msgstr "Impressão digital de %s:" #: src/service/device.js:240 msgid "Laptop" msgstr "Laptop" #: src/service/device.js:242 msgid "Smartphone" msgstr "Smartphone" #: src/service/device.js:244 msgid "Tablet" msgstr "Tablet" #: src/service/device.js:246 msgid "Desktop" msgstr "Computador" #: src/service/device.js:409 msgid "Reconnect" msgstr "Reconectar" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:606 #, javascript-format msgid "Pair Request from %s" msgstr "Convite de pareamento de %s" #: src/service/device.js:613 msgid "Reject" msgstr "Rejeitar" #: src/service/device.js:618 msgid "Accept" msgstr "Aceitar" #: src/service/plugins/battery.js:194 src/service/plugins/findmyphone.js:18 #, fuzzy msgid "Ring" msgstr "Localizar" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:203 #, javascript-format msgid "%s: Battery is low" msgstr "%s: bateria fraca" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:205 #, javascript-format msgid "%d%% remaining" msgstr "%d%% restante" #: src/service/plugins/clipboard.js:11 msgid "Clipboard" msgstr "Área de transferência" #: src/service/plugins/clipboard.js:17 msgid "Clipboard Push" msgstr "Enviar para área de transferência" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Pull" msgstr "Pegar da área de transferência" #: src/service/plugins/contacts.js:12 msgid "Contacts" msgstr "Contatos" #: src/service/plugins/findmyphone.js:12 msgid "Find My Phone" msgstr "Encontrar meu smartphone" #: src/service/plugins/findmyphone.js:65 msgid "Locate Device" msgstr "Localizar dispositivo" #: src/service/plugins/findmyphone.js:66 #, javascript-format msgid "%s asked to locate this device" msgstr "%s pediu para localizar este dispositivo" #: src/service/plugins/findmyphone.js:74 msgid "Found" msgstr "Encontrado" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Mousepad" #: src/service/plugins/mousepad.js:28 src/service/plugins/mousepad.js:578 msgid "Keyboard" msgstr "Teclado" #: src/service/plugins/mousepad.js:595 msgid "Keyboard not ready" msgstr "O teclado não está pronto" #: src/service/plugins/mpris.js:10 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/notification.js:25 msgid "Cancel Notification" msgstr "Cancelar notificação" #: src/service/plugins/notification.js:33 msgid "Close Notification" msgstr "Fechar notificação" #: src/service/plugins/notification.js:41 msgid "Reply Notification" msgstr "Responder notificação" #: src/service/plugins/notification.js:49 msgid "Send Notification" msgstr "Enviar notificação" #: src/service/plugins/ping.js:11 src/service/plugins/ping.js:17 #: src/service/plugins/ping.js:46 msgid "Ping" 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:53 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/runcommand.js:12 msgid "Run Commands" msgstr "Executar comandos" #: src/service/plugins/sftp.js:12 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:18 msgid "Mount" msgstr "Montar" #: src/service/plugins/sftp.js:26 msgid "Unmount" msgstr "Desmontar" #: src/service/plugins/sftp.js:170 msgid "All files" msgstr "Todos os arquivos" #: src/service/plugins/sftp.js:171 msgid "Camera pictures" msgstr "Fotos da câmera" #: src/service/plugins/sftp.js:333 msgid "Files" msgstr "Arquivos" #: src/service/plugins/share.js:12 src/service/plugins/share.js:18 msgid "Share" msgstr "Compartilhar" #: src/service/plugins/share.js:26 msgid "Share File" msgstr "Compartilhar arquivo" #: src/service/plugins/share.js:34 msgid "Share Text" msgstr "Compartilhar texto" #: src/service/plugins/share.js:42 src/service/ui/messaging.js:903 #: src/service/ui/messaging.js:911 msgid "Share Link" msgstr "Compartilhar link" #: src/service/plugins/share.js:131 src/service/plugins/share.js:282 msgid "Starting Transfer" msgstr "Iniciando transferência" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:133 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Recebendo “%s” de %s" #. Action Buttons #: src/service/plugins/share.js:138 src/service/plugins/share.js:289 #: src/service/plugins/share.js:407 src/service/ui/keybindings.js:44 #: src/service/ui/service.js:121 src/service/ui/service.js:300 #: src/service/ui/settings.js:873 src/shell/donotdisturb.js:215 msgid "Cancel" msgstr "Cancelar" #: src/service/plugins/share.js:149 src/service/plugins/share.js:303 msgid "Transfer Successful" msgstr "Transferência completa" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:151 #, javascript-format msgid "Received “%s” from %s" msgstr "Recebido “%s” de %s" #: src/service/plugins/share.js:157 msgid "Open Folder" msgstr "Abrir pasta" #: src/service/plugins/share.js:162 msgid "Open File" msgstr "Abrir arquivo" #: src/service/plugins/share.js:169 src/service/plugins/share.js:311 msgid "Transfer Failed" msgstr "Transferência falhou" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:171 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Falha ao receber “%s” de %s" #: src/service/plugins/share.js:211 #, 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:284 #, 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:305 #, 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:313 #, 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:384 #, javascript-format msgid "Send files to %s" msgstr "Enviar arquivos para %s" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:402 #, javascript-format msgid "Send a link to %s" msgstr "Enviar um link para %s" #: src/service/plugins/share.js:408 msgid "Send" msgstr "Enviar" #: src/service/plugins/sms.js:12 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:33 msgid "New SMS (URI)" msgstr "Novo SMS (URI)" #: src/service/plugins/sms.js:41 msgid "Reply SMS" msgstr "Responder SMS" #: src/service/plugins/sms.js:57 msgid "Share SMS" msgstr "Compartilhar SMS" #: src/service/plugins/systemvolume.js:11 msgid "System Volume" msgstr "Volume do sistema" #: src/service/plugins/telephony.js:21 msgid "Mute Call" msgstr "Silenciar chamada" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #: src/service/plugins/telephony.js:155 src/service/ui/contacts.js:96 #: src/service/ui/contacts.js:350 msgid "Unknown Contact" msgstr "Contato desconhecido" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:172 msgid "Incoming call" msgstr "Recebendo chamada" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:187 msgid "Ongoing call" msgstr "Chamada em andamento" #. TRANSLATORS: All other phone number types #: src/service/ui/contacts.js:58 src/service/ui/contacts.js:79 #, javascript-format msgid "%s・Other" msgstr "%s・Outro" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:63 #, javascript-format msgid "%s・Fax" msgstr "%s・Fax" #. TRANSLATORS: A work phone number #: src/service/ui/contacts.js:67 #, javascript-format msgid "%s・Work" msgstr "%s・Trabalho" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:71 #, javascript-format msgid "%s・Mobile" msgstr "%s・Celular" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:75 #, javascript-format msgid "%s・Home" msgstr "%s・Casa" #: src/service/ui/contacts.js:203 msgid "Select a contact or number" msgstr "Selecione um contato ou número" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #: src/service/ui/contacts.js:239 src/service/ui/contacts.js:253 #, javascript-format msgid "Send to %s" msgstr "Enviar para %s" #. TRANSLATORS: Title of keyboard shortcut dialog #: src/service/ui/keybindings.js:33 msgid "Set Shortcut" msgstr "Definir atalho" #. TRANSLATORS: Button to confirm the new shortcut #: src/service/ui/keybindings.js:47 msgid "Set" msgstr "Definir" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/service/ui/keybindings.js:54 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Digite um novo atalho para mudar %s" #. TRANSLATORS: Keys for cancelling (␛) or resetting (␈) a shortcut #: src/service/ui/keybindings.js:83 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." #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/service/ui/keybindings.js:182 #, javascript-format msgid "%s is already being used" msgstr "%s já está em uso" #: src/service/ui/service.js:122 msgid "Connect" msgstr "Conectar" #: src/service/ui/service.js:294 msgid "Select a Device" msgstr "Selecione um dispositivo" #: src/service/ui/service.js:298 msgid "Select" msgstr "Selecionar" #: src/service/ui/settings.js:298 msgid "Panel" msgstr "Painel" #: src/service/ui/settings.js:298 msgid "User Menu" msgstr "Menu do usuário" #: src/service/ui/settings.js:874 msgid "Open" msgstr "Abrir" #: src/service/ui/settings.js:931 src/service/ui/settings.js:944 msgid "On" msgstr "Ativada" #: src/service/ui/settings.js:931 src/service/ui/settings.js:944 msgid "Off" msgstr "Desativada" #: src/service/ui/settings.js:1065 src/service/ui/settings.js:1131 msgid "Disabled" msgstr "Desativado" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:93 src/service/ui/messaging.js:126 msgid "Just now" msgstr "Agora" #. TRANSLATORS: Time duration in minutes (eg. 15 minutes) #: src/service/ui/messaging.js:98 src/service/ui/messaging.js:130 #: src/shell/donotdisturb.js:266 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minuto" msgstr[1] "%d minutos" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:103 #, javascript-format msgid "Yesterday・%s" msgstr "Ontem・%s" #: src/service/ui/messaging.js:920 msgid "New Message" msgstr "Nova mensagem" #. TRANSLATORS: When the battery level is 100% #: src/shell/device.js:86 msgid "Fully Charged" msgstr "Carregado" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:90 #, 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:100 #, 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:108 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d restante)" #: src/shell/donotdisturb.js:150 src/shell/donotdisturb.js:289 msgid "Do Not Disturb" msgstr "Não perturbe" #: src/shell/donotdisturb.js:156 msgid "Silence Mobile Device Notifications" msgstr "Silenciar notificações de dispositivos móveis" #: src/shell/donotdisturb.js:171 msgid "Until you turn off Do Not Disturb" msgstr "Até que você desative o \"Não perturbe\"" #. TRANSLATORS: Time until change with time duration #. EXAMPLE: Until 10:00 (2 hours) #: src/shell/donotdisturb.js:184 src/shell/donotdisturb.js:278 #, javascript-format msgid "Until %s (%s)" msgstr "Até %s (%s)" #: src/shell/donotdisturb.js:216 msgid "Done" msgstr "Pronto" #. TRANSLATORS: Time duration in hours (eg. 2 hours) #: src/shell/donotdisturb.js:263 #, javascript-format msgid "One hour" msgid_plural "%d hours" msgstr[0] "Uma hora" msgstr[1] "%d horas" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:29 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:33 msgid "Service Unavailable" msgstr "Serviço indisponível" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:37 msgid "Open in Browser" msgstr "Abrir no navegador" gnome-shell-extension-gsconnect-20/po/ru.po000066400000000000000000000655011341554142200211160ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: gsconnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-12-29 16:44-0800\n" "PO-Revision-Date: 2019-01-08 20:42\n" "Last-Translator: andyholmes \n" "Language-Team: Russian\n" "Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=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-Generator: crowdin.com\n" "X-Crowdin-Project: gsconnect\n" "X-Crowdin-Language: ru\n" "X-Crowdin-File: /master/po/org.gnome.Shell.Extensions.GSConnect.pot\n" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/connect.ui:24 data/menus.ui:7 msgid "Connect to…" msgstr "Подключиться к…" #. Action Buttons #: data/connect.ui:30 data/notification.ui:14 data/telephony.ui:14 #: src/service/plugins/share.js:139 src/service/plugins/share.js:310 #: src/service/plugins/share.js:456 src/service/ui/device.js:607 #: src/service/ui/keybindings.js:44 src/service/ui/service.js:37 #: src/service/ui/settings.js:539 src/shell/donotdisturb.js:215 msgid "Cancel" msgstr "Отмена" #: data/connect.ui:37 msgid "Connect" msgstr "Подключиться" #: data/connect.ui:98 msgid "IP Address" msgstr "IP адрес" #: data/connect.ui:142 msgid "Bluetooth Device" msgstr "Bluetooth устройство" #: data/contacts.ui:49 msgid "Type a phone number or name" msgstr "Наберите номер или имя" #: data/contacts.ui:83 msgid "No contacts" msgstr "Нет контактов" #: data/contacts.ui:95 data/menus.ui:33 data/messaging.ui:247 msgid "Help" msgstr "Помощь" #: data/conversation.ui:76 data/conversation.ui:85 src/shell/notification.js:51 msgid "Type a message" msgstr "Набрать сообщение" #: data/conversation.ui:84 msgid "Send Message" msgstr "Отправить сообщение" #: data/device.ui:67 src/service/plugins/battery.js:12 msgid "Battery" msgstr "Батарея" #: data/device.ui:122 msgid "Clipboard Sync" msgstr "Синхронизация буфера обмена" #: data/device.ui:185 msgid "Media Players" msgstr "Проигрыватели" #: data/device.ui:240 msgid "Mouse & Keyboard" msgstr "Мышь и клавиатура" #: data/device.ui:295 msgid "Volume Control" msgstr "Управление громкостью" #: data/device.ui:345 data/device.ui:1728 msgid "Sharing" msgstr "Общий доступ и обмен" #: data/device.ui:374 data/device.ui:651 data/device.ui:1774 #: src/service/plugins/runcommand.js:18 src/service/plugins/runcommand.js:169 msgid "Commands" msgstr "Команды" #: data/device.ui:424 data/device.ui:427 msgid "Name" msgstr "Имя" #: data/device.ui:440 data/device.ui:446 msgid "Command Line" msgstr "Командная строка" #: data/device.ui:444 data/device.ui:445 msgid "Choose an executable" msgstr "Выберите исполняемый файл" #: data/device.ui:496 data/device.ui:511 msgid "Add" msgstr "Добавить" #: data/device.ui:527 data/device.ui:542 msgid "Remove" msgstr "Удалить" #: data/device.ui:576 data/device.ui:588 msgid "Edit" msgstr "Изменить" #: data/device.ui:604 data/device.ui:616 msgid "Save" msgstr "Сохранить" #: data/device.ui:712 msgid "Share Notifications" msgstr "Отправлять уведомления" #: data/device.ui:763 msgid "Applications" msgstr "Приложения" #: data/device.ui:809 data/device.ui:1820 #: src/service/plugins/notification.js:13 msgid "Notifications" msgstr "Уведомления" #: data/device.ui:839 msgid "Incoming Calls" msgstr "При входящем вызове" #: data/device.ui:888 data/device.ui:1055 msgid "Volume" msgstr "Громкость" #: data/device.ui:954 data/device.ui:1120 msgid "Pause Media" msgstr "Приостановить плеер" #: data/device.ui:1007 msgid "Ongoing Calls" msgstr "При исходящем вызове" #: data/device.ui:1176 msgid "Mute Microphone" msgstr "Выключить микрофон" #: data/device.ui:1230 data/device.ui:1866 src/service/plugins/telephony.js:13 msgid "Telephony" msgstr "Телефония" #: data/device.ui:1265 msgid "Action Shortcuts" msgstr "Комбинации клавиш" #: data/device.ui:1280 data/device.ui:1349 msgid "Reset All…" msgstr "Сбросить все…" #: data/device.ui:1334 msgid "Command Shortcuts" msgstr "Комбинации клавиш" #: data/device.ui:1398 msgid "Shortcuts" msgstr "Комбинации клавиш" #: data/device.ui:1429 msgid "Plugins" msgstr "Плагины" #: data/device.ui:1475 msgid "Experimental" msgstr "Экспериментальное" #: data/device.ui:1524 msgid "Legacy SMS Support" msgstr "Устаревшая поддержка SMS" #: data/device.ui:1598 msgid "Delete" msgstr "Удалить" #: data/device.ui:1627 msgid "Delete this device" msgstr "Удалить устройство" #: data/device.ui:1645 msgid "Unpair and remove all settings and files" msgstr "Забыть и удалить все настройки и файлы" #: data/device.ui:1678 data/device.ui:1958 msgid "Advanced" msgstr "Дополнительные" #: data/device.ui:1912 msgid "Keyboard Shortcuts" msgstr "Комбинации клавиш" #. TRANSLATORS: Send a pair request to the device #: data/device.ui:2015 data/menus.ui:68 src/service/device.js:424 msgid "Pair" msgstr "Сопряжение" #: data/device.ui:2047 msgid "Device is unpaired" msgstr "Устройство не сопряжено" #: data/device.ui:2062 msgid "You may configure this device before pairing" msgstr "Вы можете настроить это устройство перед сопряжением" #: data/menus.ui:12 msgid "Display Mode" msgstr "Режим отображения" #. TRANSLATORS: Show device indicators in the top bar #: data/menus.ui:15 msgid "Panel" msgstr "Панель" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/menus.ui:21 msgid "User Menu" msgstr "Меню пользователя" #. TRANSLATORS: Generate a support log #: data/menus.ui:29 src/service/ui/settings.js:536 msgid "Generate Support Log" msgstr "Сгенерировать журнал" #: data/menus.ui:38 msgid "About" msgstr "О Расширении" #. TRANSLATORS: Change the connection type to Bluetooth #: data/menus.ui:49 msgid "Switch to Bluetooth" msgstr "Переключиться на Bluetooth" #. TRANSLATORS: Change the connection type to TCP/IP #: data/menus.ui:56 msgid "Switch to LAN" msgstr "Переключиться на LAN" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/menus.ui:63 src/service/ui/device.js:317 msgid "Encryption Info" msgstr "Информация о шифровании" #. TRANSLATORS: Unpair the device and notify it #: data/menus.ui:74 src/service/device.js:432 msgid "Unpair" msgstr "Забыть" #. TRANSLATORS: Send clipboard content to device #: data/menus.ui:86 msgid "To Device" msgstr "На устройство" #. TRANSLATORS: Receive clipboard content from the device #: data/menus.ui:92 msgid "From Device" msgstr "С устройства" #. TRANSLATORS: Don't change the system volume #: data/menus.ui:104 data/menus.ui:130 msgid "Nothing" msgstr "Ничего" #. TRANSLATORS: Lower the system volume #: data/menus.ui:111 data/menus.ui:137 msgid "Lower" msgstr "Тише" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the phone ringer #: data/menus.ui:118 data/menus.ui:144 src/service/plugins/telephony.js:194 msgid "Mute" msgstr "Выключить" #: data/messaging.ui:12 src/service/plugins/sms.js:26 #: src/service/ui/messaging.js:911 msgid "Messaging" msgstr "Сообщение" #: data/messaging.ui:21 src/service/ui/messaging.js:992 msgid "New Conversation" msgstr "Новый диалог" #: data/messaging.ui:110 msgid "No conversation selected" msgstr "Не выбран диалог" #: data/messaging.ui:126 msgid "Select or start a conversation" msgstr "Выберите или начните диалог" #: data/messaging.ui:193 data/notification.ui:52 data/telephony.ui:52 msgid "Device is disconnected" msgstr "Устройство отключено" #: data/messaging.ui:264 msgid "No Conversations" msgstr "Нет диалогов" #: data/notification.ui:21 data/telephony.ui:21 #: src/service/plugins/share.js:457 msgid "Send" msgstr "Отправить" #: data/settings.ui:10 msgid "Searching for devices…" msgstr "Поиск устройств…" #: data/settings.ui:49 data/settings.ui:63 msgid "Refresh" msgstr "Обновить" #: data/settings.ui:74 data/settings.ui:91 src/extension.js:105 msgid "Mobile Settings" msgstr "Настройки" #: data/settings.ui:144 data/settings.ui:158 msgid "Edit Device Name" msgstr "Редактировать название устройства" #: data/settings.ui:236 msgid "Devices" msgstr "Устройства" #: data/settings.ui:299 msgid "Browser Add-Ons" msgstr "Расширения браузера" #: data/settings.ui:579 msgid "Enable" msgstr "Включить" #: data/settings.ui:611 msgid "This device is invisible to unpaired devices" msgstr "Это устройство является невидимым для неспаренных устройств" #: data/settings.ui:623 src/service/daemon.js:532 msgid "Discovery Disabled" msgstr "Обнаружение выключено" #. TRANSLATORS: Share URL by SMS #: data/telephony.ui:9 src/service/daemon.js:718 src/service/plugins/sms.js:50 #: webextension/gettext.js:39 msgid "Send SMS" msgstr "Отправить СМС" #. Service Menu #: src/extension.js:79 src/extension.js:255 msgid "Mobile Devices" msgstr "Устройства" #. TRANSLATORS: %d is the number of devices connected #: src/extension.js:253 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d подключен" msgstr[1] "%d подключено" msgstr[2] "%d подключено" msgstr[3] "%d подключено" #. TRANSLATORS: Top-level context menu item for GSConnect #: src/nautilus-gsconnect.py:112 webextension/gettext.js:31 msgid "Send To Mobile Device" msgstr "Отправить на устройство" #: src/service/daemon.js:372 msgid "Report" msgstr "Отзыв" #: src/service/daemon.js:507 msgid "Authentication Failure" msgstr "Ошибка аутентификации" #: src/service/daemon.js:516 msgid "Network Error" msgstr "Ошибка сети" #: src/service/daemon.js:517 src/service/daemon.js:525 msgid "Click for help troubleshooting" msgstr "Нажмите для решения проблем" #: src/service/daemon.js:524 msgid "PulseAudio Error" msgstr "Ошибка Pulseaudio" #: src/service/daemon.js:533 msgid "Discovery has been disabled due to the number of devices on this network." msgstr "Обнаружение было выключено из-за количества устройств в этой сети." #: src/service/daemon.js:541 #, javascript-format msgid "%s Plugin Failed To Load" msgstr "Ошибка загрузки плагина %s" #: src/service/daemon.js:542 msgid "Click for more information" msgstr "Нажмите для получения информации" #: src/service/daemon.js:724 msgid "Dial Number" msgstr "Вызвать номер" #: src/service/daemon.js:730 src/service/plugins/share.js:27 msgid "Share File" msgstr "Поделиться файлом" #: src/service/device.js:161 msgid "Not available" msgstr "Недоступно" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:165 #, javascript-format msgid "Bluetooth device at %s" msgstr "Bluetooth устройство на %s" #. TRANSLATORS: Label for TLS Certificate fingerprint #. #. Example: #. #. Google Pixel Fingerprint: #. 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 #: src/service/device.js:183 src/service/device.js:185 #, javascript-format msgid "%s Fingerprint:" msgstr "Отпечаток %s:" #: src/service/device.js:242 msgid "Laptop" msgstr "Ноутбук" #: src/service/device.js:244 msgid "Smartphone" msgstr "Смартфон" #: src/service/device.js:246 msgid "Tablet" msgstr "Планшет" #: src/service/device.js:248 msgid "Desktop" msgstr "Компьютер" #: src/service/device.js:408 src/service/ui/device.js:15 msgid "Reconnect" msgstr "Переподключиться" #: src/service/device.js:416 src/service/ui/device.js:16 #: src/service/ui/settings.js:371 msgid "Settings" msgstr "Настройки" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:607 #, javascript-format msgid "Pair Request from %s" msgstr "Запрос сопряжения от %s" #: src/service/device.js:614 msgid "Reject" msgstr "Отклонить" #: src/service/device.js:619 msgid "Accept" msgstr "Принять" #: src/service/plugins/battery.js:188 src/service/plugins/findmyphone.js:19 msgid "Ring" msgstr "Найти" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:197 #, javascript-format msgid "%s: Battery is low" msgstr "%s: Аккумулятор разряжен" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:199 #, javascript-format msgid "%d%% remaining" msgstr "%d%% осталось" #: src/service/plugins/clipboard.js:11 msgid "Clipboard" msgstr "Буфер обмена" #: src/service/plugins/clipboard.js:17 msgid "Clipboard Push" msgstr "Отправить Буфер обмена" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Pull" msgstr "Запросить Буфер обмена" #: src/service/plugins/contacts.js:12 msgid "Contacts" msgstr "Контакты" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #: src/service/plugins/contacts.js:263 src/service/plugins/telephony.js:173 #: src/service/plugins/telephony.js:224 src/service/plugins/telephony.js:264 #: src/service/ui/contacts.js:156 src/service/ui/contacts.js:397 msgid "Unknown Contact" msgstr "Неизвестный контакт" #: src/service/plugins/findmyphone.js:13 msgid "Find My Phone" msgstr "Найти мой смартфон" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Тачпад" #: src/service/plugins/mousepad.js:28 src/service/plugins/mousepad.js:569 msgid "Keyboard" msgstr "Клавиатура" #: src/service/plugins/mousepad.js:223 msgid "Additional Software Required" msgstr "Необходимо дополнительное ПО" #: src/service/plugins/mousepad.js:586 msgid "Keyboard not ready" msgstr "Клавиатура не готова" #: src/service/plugins/mpris.js:10 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/notification.js:26 msgid "Cancel Notification" msgstr "Отменить уведомление" #: src/service/plugins/notification.js:34 msgid "Close Notification" msgstr "Закрыть уведомление" #: src/service/plugins/notification.js:42 msgid "Reply Notification" msgstr "Ответить" #: src/service/plugins/notification.js:50 msgid "Send Notification" msgstr "Отправить" #: src/service/plugins/ping.js:11 src/service/plugins/ping.js:17 #: src/service/plugins/ping.js:46 msgid "Ping" msgstr "Пинг" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:53 #, javascript-format msgid "Ping: %s" msgstr "Пинг: %s" #: src/service/plugins/runcommand.js:12 msgid "Run Commands" msgstr "Отправить Команду" #: src/service/plugins/sftp.js:12 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:18 msgid "Mount" msgstr "Примонтировать" #: src/service/plugins/sftp.js:26 msgid "Unmount" msgstr "Размонтировать" #: src/service/plugins/sftp.js:159 msgid "All files" msgstr "Все файлы" #: src/service/plugins/sftp.js:160 msgid "Camera pictures" msgstr "Фотографии" #: src/service/plugins/sftp.js:417 msgid "Files" msgstr "Файлы" #: src/service/plugins/share.js:13 src/service/plugins/share.js:19 msgid "Share" msgstr "Поделиться" #: src/service/plugins/share.js:35 msgid "Share Text" msgstr "Поделиться текстом" #: src/service/plugins/share.js:43 src/service/ui/messaging.js:975 #: src/service/ui/messaging.js:983 msgid "Share Link" msgstr "Поделиться ссылкой" #: src/service/plugins/share.js:132 src/service/plugins/share.js:303 msgid "Starting Transfer" msgstr "Начало передачи" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:134 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Получение «%s» от %s" #: src/service/plugins/share.js:172 src/service/plugins/share.js:327 msgid "Transfer Successful" msgstr "Передача завершена" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:174 #, javascript-format msgid "Received “%s” from %s" msgstr "Получен «%s» от %s" #: src/service/plugins/share.js:180 msgid "Open Folder" msgstr "Открыть папку" #: src/service/plugins/share.js:185 msgid "Open File" msgstr "Открыть файл" #: src/service/plugins/share.js:192 src/service/plugins/share.js:335 msgid "Transfer Failed" msgstr "Передача не удалась" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:194 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Не удалось получить«%s» от %s" #: src/service/plugins/share.js:233 #, javascript-format msgid "Text Shared By %s" msgstr "Текст получен с %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:305 #, javascript-format msgid "Sending “%s” to %s" msgstr "Отправка «%s» на %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:329 #, 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:337 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "Не удалось отправить «%s» на %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:408 #, javascript-format msgid "Send files to %s" msgstr "Отправить файлы на %s" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:412 msgid "Open when done" msgstr "Открыть при завершении" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:451 #, javascript-format msgid "Send a link to %s" msgstr "Отправить ссылку на %s" #: src/service/plugins/sms.js:13 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:34 msgid "New SMS (URI)" msgstr "Новое сообщение (URI)" #: src/service/plugins/sms.js:42 src/service/plugins/telephony.js:22 msgid "Reply SMS" msgstr "Ответить на SMS" #: src/service/plugins/sms.js:58 msgid "Share SMS" msgstr "Отправить SMS" #: src/service/plugins/systemvolume.js:11 msgid "System Volume" msgstr "Громкость" #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Отключить звонок" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:190 msgid "Incoming call" msgstr "Входящий звонок" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:205 msgid "Ongoing call" msgstr "Исходящий звонок" #. TRANSLATORS: All other phone number types #: src/service/ui/contacts.js:118 src/service/ui/contacts.js:139 #, javascript-format msgid "%s・Other" msgstr "%s・Другой" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:123 #, javascript-format msgid "%s・Fax" msgstr "%s・Факс" #. TRANSLATORS: A work phone number #: src/service/ui/contacts.js:127 #, javascript-format msgid "%s・Work" msgstr "%s・Рабочий" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:131 #, javascript-format msgid "%s・Mobile" msgstr "%s・Мобильный" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:135 #, javascript-format msgid "%s・Home" msgstr "%s・Домашний" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #: src/service/ui/contacts.js:278 src/service/ui/contacts.js:292 #, javascript-format msgid "Send to %s" msgstr "Отправить на %s" #: src/service/ui/device.js:608 msgid "Open" msgstr "Открыть" #: src/service/ui/device.js:660 src/service/ui/device.js:673 msgid "On" msgstr "Вкл" #: src/service/ui/device.js:660 src/service/ui/device.js:673 msgid "Off" msgstr "Выкл" #: src/service/ui/device.js:798 src/service/ui/device.js:826 #: src/service/ui/device.js:850 msgid "Disabled" msgstr "Отключено" #. TRANSLATORS: Title of keyboard shortcut dialog #: src/service/ui/keybindings.js:33 msgid "Set Shortcut" msgstr "Установить комбинацию клавиш" #. TRANSLATORS: Button to confirm the new shortcut #: src/service/ui/keybindings.js:47 msgid "Set" msgstr "Выбор" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/service/ui/keybindings.js:54 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Введите новую комбинацию клавиш для %s" #. TRANSLATORS: Keys for cancelling (␛) or resetting (␈) a shortcut #: src/service/ui/keybindings.js:83 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "Нажмите Esc для отмены или Backspace чтобы сбросить комбинацию." #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/service/ui/keybindings.js:182 #, javascript-format msgid "%s is already being used" msgstr "%s уже используется" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:65 src/service/ui/messaging.js:98 msgid "Just now" msgstr "Только что" #. TRANSLATORS: Time duration in minutes (eg. 15 minutes) #: src/service/ui/messaging.js:70 src/service/ui/messaging.js:102 #: src/shell/donotdisturb.js:266 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d минута" msgstr[1] "%d минуты" msgstr[2] "%d минут" msgstr[3] "%d минуты" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:75 #, javascript-format msgid "Yesterday・%s" msgstr "Вчера・%s" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:243 #, javascript-format msgid "You: %s" msgstr "Вы: %s" #: src/service/ui/service.js:31 msgid "Select a Device" msgstr "Выберите устройство" #: src/service/ui/service.js:35 msgid "Select" msgstr "Выбор" #: src/service/ui/settings.js:331 msgid "Unpaired" msgstr "Не сопряжен" #: src/service/ui/settings.js:333 msgid "Disconnected" msgstr "Отключено" #: src/service/ui/settings.js:336 msgid "Connected" msgstr "Подключено" #. TRANSLATORS: Description of where directly shared files are stored. #: src/service/ui/settings.js:406 #, javascript-format msgid "Transferred files are placed in the Downloads folder." msgstr "Переданные файлы помещаются в папку Загрузки." #: src/service/ui/settings.js:499 msgid "A complete KDE Connect implementation for GNOME" msgstr "Полная реализация KDE Connect для GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/service/ui/settings.js:508 msgid "translator-credits" msgstr "'Losted' " #: src/service/ui/settings.js:537 msgid "Debug messages are being logged. Take any steps necessary to reproduce a problem then review the log." msgstr "Отладочные сообщения будут записаны. Произведите действия при которых произошла проблема, затем посмотрите журнал." #: src/service/ui/settings.js:540 msgid "Review Log" msgstr "Посмотреть журнал" #. TRANSLATORS: When the battery level is 100% #: src/shell/device.js:82 msgid "Fully Charged" msgstr "Полностью заряжено" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:86 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (Осталось…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:96 #, 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:104 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d Осталось)" #: src/shell/donotdisturb.js:150 src/shell/donotdisturb.js:289 msgid "Do Not Disturb" msgstr "Не беспокоить" #: src/shell/donotdisturb.js:156 msgid "Silence Mobile Device Notifications" msgstr "Отключить уведомления с устройства" #: src/shell/donotdisturb.js:171 msgid "Until you turn off Do Not Disturb" msgstr "Пока вы не отключите режим Не беспокоить" #. TRANSLATORS: Time until change with time duration #. EXAMPLE: Until 10:00 (2 hours) #: src/shell/donotdisturb.js:184 src/shell/donotdisturb.js:278 #, javascript-format msgid "Until %s (%s)" msgstr "До %s (%s)" #: src/shell/donotdisturb.js:216 msgid "Done" msgstr "Готово" #. TRANSLATORS: Time duration in hours (eg. 2 hours) #: src/shell/donotdisturb.js:263 #, javascript-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "%d час" msgstr[1] "%d часа" msgstr[2] "%d часов" msgstr[3] "%d часа" #: src/shell/notification.js:42 msgid "Reply" msgstr "Ответить" #. TRANSLATORS: Extension name #: webextension/gettext.js:27 msgid "GSConnect" msgstr "GSConnect" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:29 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "Отправлять ссылки в веб браузер или по SMS." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:33 msgid "Service Unavailable" msgstr "Сервис недоступен" #. TRANSLATORS: No devices are known or available #: webextension/gettext.js:35 msgid "No Device Found" msgstr "Устройства не найдены" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:37 msgid "Open in Browser" msgstr "Открыть в браузере" gnome-shell-extension-gsconnect-20/po/sk.po000066400000000000000000000550471341554142200211110ustar00rootroot00000000000000# 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. # msgid "" msgstr "" "Project-Id-Version: org.gnome.Shell.Extensions.GSConnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-11-30 14:46+0100\n" "PO-Revision-Date: 2018-11-30 15:56+0100\n" "Language-Team: \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\n" "Last-Translator: Dušan Kazik \n" "Language: sk\n" #: data/conversation.ui:73 data/conversation.ui:81 msgid "Type a message" msgstr "Zadajte správu" #: data/contacts.ui:51 data/contacts.ui:52 msgid "Type a phone number or name" msgstr "Zadajte telefónne číslo alebo meno" #: data/device.ui:68 src/service/plugins/battery.js:12 msgid "Battery" msgstr "Batéria" #: data/device.ui:116 msgid "Clipboard Sync" msgstr "Synchronizácia schránky" #: data/device.ui:172 msgid "Media Players" msgstr "Multimediálne prehrávače" #: data/device.ui:220 msgid "Mouse & Keyboard" msgstr "Myš a klávesnica" #: data/device.ui:268 msgid "Volume Control" msgstr "Ovládanie hlasitosti" #: data/device.ui:308 data/device.ui:1406 msgid "Sharing" msgstr "Zdieľanie" #: data/device.ui:337 data/device.ui:534 data/device.ui:1449 #: src/service/plugins/runcommand.js:18 src/service/plugins/runcommand.js:169 msgid "Commands" msgstr "Príkazy" #: data/device.ui:385 msgid "Name" msgstr "Názov" #: data/device.ui:401 data/device.ui:402 msgid "Choose an executable" msgstr "Zvolí spustiteľný súbor" #: data/device.ui:403 msgid "Command Line" msgstr "Príkazový riadok" #: data/device.ui:597 msgid "Share Notifications" msgstr "Zdieľať oznámenia" #: data/device.ui:636 msgid "Applications" msgstr "Aplikácie" #: data/device.ui:675 data/device.ui:1492 #: src/service/plugins/notification.js:12 msgid "Notifications" msgstr "Oznámenia" #: data/device.ui:705 msgid "Incoming Calls" msgstr "Prichádzajúce hovory" #: data/device.ui:755 data/device.ui:900 msgid "Volume" msgstr "Hlasitosť" #: data/device.ui:813 data/device.ui:958 msgid "Pause Media" msgstr "Pozastaviť médiá" #: data/device.ui:852 msgid "Ongoing Calls" msgstr "Odchádzajúce hovory" #: data/device.ui:1007 msgid "Mute Microphone" msgstr "Stíšiť mikrofón" #: data/device.ui:1047 data/device.ui:1535 src/service/plugins/telephony.js:12 msgid "Telephony" msgstr "Telefonovanie" #: data/device.ui:1082 msgid "Action Shortcuts" msgstr "Skratky akcií" #: data/device.ui:1094 data/device.ui:1157 msgid "Reset All…" msgstr "Obnoviť všetko…" #: data/device.ui:1145 msgid "Command Shortcuts" msgstr "Skratky príkazov" #: data/device.ui:1203 msgid "Shortcuts" msgstr "Skratky" #: data/device.ui:1234 msgid "Plugins" msgstr "Zásuvné moduly" #: data/device.ui:1295 msgid "Delete" msgstr "Odstrániť" #: data/device.ui:1320 msgid "Delete this device" msgstr "Odstránenie tohoto zariadenia" #: data/device.ui:1335 msgid "Unpair and remove all settings and files" msgstr "Zruší párovanie a odstráni všetky nastavenia a súbory" #: data/device.ui:1365 data/device.ui:1621 msgid "Advanced" msgstr "Pokročilé" #: data/device.ui:1578 msgid "Keyboard Shortcuts" msgstr "Klávesové skratky" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/menus.ui:7 src/service/ui/service.js:115 msgid "Connect to…" msgstr "Pripojiť k…" #: data/menus.ui:13 data/messaging.ui:244 data/settings.ui:669 #: data/settings.ui:735 data/settings.ui:801 src/service/ui/settings.js:337 msgid "Help" msgstr "Pomocník" #. TRANSLATORS: Open the developer's dialog #: data/menus.ui:19 msgid "Debugger" msgstr "Ladiaci nástroj" #: data/menus.ui:23 msgid "About" msgstr "O aplikácii" #. TRANSLATORS: Change the connection type to Bluetooth #: data/menus.ui:34 msgid "Switch to Bluetooth" msgstr "Prepnúť na Bluetooth" #. TRANSLATORS: Change the connection type to TCP/IP #: data/menus.ui:41 msgid "Switch to LAN" msgstr "Prepnúť na LAN" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/menus.ui:48 src/service/ui/settings.js:687 msgid "Encryption Info" msgstr "Informácie o šifrovaní" #. TRANSLATORS: Send a pair request to the device #: data/menus.ui:53 src/service/device.js:432 msgid "Pair" msgstr "Spárovať" #. TRANSLATORS: Unpair the device and notify it #: data/menus.ui:59 src/service/device.js:440 msgid "Unpair" msgstr "Zrušiť párovanie" #. TRANSLATORS: Send clipboard content to device #: data/menus.ui:71 msgid "To Device" msgstr "Do zariadenia" #. TRANSLATORS: Receive clipboard content from the device #: data/menus.ui:77 msgid "From Device" msgstr "Zo zariadenia" #. TRANSLATORS: Don't change the system volume #: data/menus.ui:89 data/menus.ui:115 msgid "Nothing" msgstr "Bez zmeny" #. TRANSLATORS: Lower the system volume #: data/menus.ui:96 data/menus.ui:122 msgid "Lower" msgstr "Tichšie" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the phone ringer #: data/menus.ui:103 data/menus.ui:129 src/service/plugins/telephony.js:175 msgid "Mute" msgstr "Stíšiť" #: data/messaging.ui:12 src/service/plugins/sms.js:25 #: src/service/ui/messaging.js:873 msgid "Messaging" msgstr "Písanie správ" #: data/messaging.ui:111 msgid "Select a conversation" msgstr "Vyberte rozhovor" #: data/messaging.ui:189 msgid "Device is disconnected" msgstr "Zariadenie je odpojené" #: data/settings.ui:334 msgid "Appearance" msgstr "Vzhľad" #: data/settings.ui:384 msgid "Display Mode" msgstr "Režim zobrazenia" #: data/settings.ui:426 msgid "Service" msgstr "Služba" #: data/settings.ui:476 msgid "Discoverable" msgstr "Objaviteľná" #: data/settings.ui:529 msgid "Restart Service" msgstr "Reštartovať službu" #: data/settings.ui:582 data/settings.ui:1016 src/service/device.js:424 msgid "Settings" msgstr "Nastavenia" #: data/settings.ui:640 msgid "Remote Filesystems" msgstr "Vzdialené súborové systémy" #: data/settings.ui:707 msgid "Extended Keyboard Support" msgstr "Rozšírená podpora klávesnice" #: data/settings.ui:773 msgid "Files Integration" msgstr "Integrácia v aplikácii Súbory" #: data/settings.ui:830 msgid "Additional Features" msgstr "Dodatočné funkcie" #: data/settings.ui:905 msgid "Browser Add-Ons" msgstr "Doplnky prehliadačov" #: data/settings.ui:923 msgid "KDE Connect" msgstr "KDE Connect" #: data/settings.ui:957 data/settings.ui:1059 msgid "Other" msgstr "Ostatné" #. TRANSLATORS: No devices are known or available #: data/settings.ui:1107 webextension/gettext.js:35 msgid "No Device Found" msgstr "Nenašlo sa žiadne zariadenie" #: data/settings.ui:1143 msgid "Refresh" msgstr "Obnoviť" #. Service Menu #: src/extension.js:79 src/extension.js:255 msgid "Mobile Devices" msgstr "Mobilné zariadenia" #: src/extension.js:105 msgid "Mobile Settings" msgstr "Mobilné nastavenia" #. TRANSLATORS: %d is the number of devices connected #: src/extension.js:253 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d pripojené" msgstr[1] "%d pripojené" msgstr[2] "%d pripojených" #. TRANSLATORS: Top-level context menu item for GSConnect #: src/nautilus-gsconnect.py:112 webextension/gettext.js:31 msgid "Send To Mobile Device" msgstr "Odoslať do mobilného zariadenia" #. TRANSLATORS: Extension name #: src/service/daemon.js:95 src/service/daemon.js:415 #: webextension/gettext.js:27 msgid "GSConnect" msgstr "GSConnect" #: src/service/daemon.js:408 msgid "A complete KDE Connect implementation for GNOME" msgstr "Kompletná implementácia aplikácie KDE Connect pre prostredie GNOME" #. TRANSLATORS: eg. 'Translator Name ' #: src/service/daemon.js:417 msgid "translator-credits" msgstr "Dušan Kazik " #: src/service/daemon.js:439 msgid "Report" msgstr "Ohlásiť" #: src/service/daemon.js:557 msgid "Authentication Failure" msgstr "Zlyhalo overenie totožnosti" #: src/service/daemon.js:566 msgid "Network Error" msgstr "Sieťová chyba" #: src/service/daemon.js:567 src/service/daemon.js:577 msgid "Click for help troubleshooting" msgstr "Kliknutím získate pomoc pri riešení problému" #: src/service/daemon.js:576 msgid "PulseAudio Error" msgstr "Chyba systému PulseAudio" #: src/service/daemon.js:586 msgid "Discovery Disabled" msgstr "Objavenie zakázané" #: src/service/daemon.js:587 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/daemon.js:589 msgid "Click to open preferences" msgstr "Kliknutím otvoríte nastavenia" #: src/service/daemon.js:597 #, javascript-format msgid "%s Plugin Failed To Load" msgstr "Zlyhalo načítanie zásuvného modulu %s" #: src/service/daemon.js:598 msgid "Click for more information" msgstr "Kliknutím získate viac informácií" #. Create an urgent notification #: src/service/daemon.js:626 #, javascript-format msgid "GSConnect: %s" msgstr "GSConnect: %s" #. TRANSLATORS: Share URL by SMS #: src/service/daemon.js:776 src/service/plugins/sms.js:49 #: webextension/gettext.js:39 msgid "Send SMS" msgstr "Odoslať SMS" #: src/service/daemon.js:780 msgid "Dial Number" msgstr "Vytočiť číslo" #: src/service/device.js:166 msgid "Not available" msgstr "Nedostupný" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:170 #, javascript-format msgid "Bluetooth device at %s" msgstr "Zariadenie Bluetooth na adrese %s" #. TRANSLATORS: Label for TLS Certificate fingerprint #. #. Example: #. #. Google Pixel Fingerprint: #. 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 #: src/service/device.js:188 src/service/device.js:190 #, javascript-format msgid "%s Fingerprint:" msgstr "Odtlačok zariadenia %s" #: src/service/device.js:247 msgid "Laptop" msgstr "Notebook" #: src/service/device.js:249 msgid "Smartphone" msgstr "Smartphone" #: src/service/device.js:251 msgid "Tablet" msgstr "Tablet" #: src/service/device.js:253 msgid "Desktop" msgstr "Stolný počítač" #: src/service/device.js:416 msgid "Reconnect" msgstr "Znovu pripojiť" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:613 #, javascript-format msgid "Pair Request from %s" msgstr "Požiadavka na spárovanie zo zariadenia %s" #: src/service/device.js:620 msgid "Reject" msgstr "Odmietnuť" #: src/service/device.js:625 msgid "Accept" msgstr "Prijať" #: src/service/plugins/battery.js:194 src/service/plugins/findmyphone.js:19 msgid "Ring" msgstr "Zvoniť" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:203 #, javascript-format msgid "%s: Battery is low" msgstr "%s: Batéria je vybitá" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:205 #, javascript-format msgid "%d%% remaining" msgstr "Zostáva %d%%" #: src/service/plugins/clipboard.js:11 msgid "Clipboard" msgstr "Schránka" #: src/service/plugins/clipboard.js:17 msgid "Clipboard Push" msgstr "Odoslať obsah schránky" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Pull" msgstr "Prijať obsah schránky" #: src/service/plugins/contacts.js:12 msgid "Contacts" msgstr "Kontakty" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #: src/service/plugins/contacts.js:191 src/service/plugins/telephony.js:154 #: src/service/ui/contacts.js:156 src/service/ui/contacts.js:415 msgid "Unknown Contact" msgstr "Neznámy kontakt" #: src/service/plugins/findmyphone.js:13 msgid "Find My Phone" msgstr "Nájdenie môjho telefónu" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Ovládanie myši" #: src/service/plugins/mousepad.js:28 src/service/plugins/mousepad.js:568 msgid "Keyboard" msgstr "Klávesnica" #: src/service/plugins/mousepad.js:223 src/service/plugins/sftp.js:69 msgid "Additional Software Required" msgstr "Vyžaduje sa dodatočný softvér" #: src/service/plugins/mousepad.js:585 msgid "Keyboard not ready" msgstr "Klávesnica nie je pripravená" #: src/service/plugins/mpris.js:10 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/notification.js:25 msgid "Cancel Notification" msgstr "Zrušiť oznámenie" #: src/service/plugins/notification.js:33 msgid "Close Notification" msgstr "Zavrieť oznámenie" #: src/service/plugins/notification.js:41 msgid "Reply Notification" msgstr "Odpovedať na oznámenie" #: src/service/plugins/notification.js:49 msgid "Send Notification" msgstr "Odoslať oznámenie" #: src/service/plugins/ping.js:11 src/service/plugins/ping.js:17 #: src/service/plugins/ping.js:46 msgid "Ping" 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:53 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/runcommand.js:12 msgid "Run Commands" msgstr "Spustiť príkazy" #: src/service/plugins/sftp.js:12 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:18 msgid "Mount" msgstr "Pripojiť" #: src/service/plugins/sftp.js:26 msgid "Unmount" msgstr "Odpojiť" #: src/service/plugins/sftp.js:168 msgid "All files" msgstr "Všetky súbory" #: src/service/plugins/sftp.js:169 msgid "Camera pictures" msgstr "Snímky fotoaparátu" #: src/service/plugins/sftp.js:331 msgid "Files" msgstr "Súbory" #: src/service/plugins/share.js:13 src/service/plugins/share.js:19 msgid "Share" msgstr "Zdieľať" #: src/service/plugins/share.js:27 msgid "Share File" msgstr "Zdieľať súbor" #: src/service/plugins/share.js:35 msgid "Share Text" msgstr "Zdieľať text" #: src/service/plugins/share.js:43 src/service/ui/messaging.js:905 #: src/service/ui/messaging.js:913 msgid "Share Link" msgstr "Zdieľať odkaz" #: src/service/plugins/share.js:132 src/service/plugins/share.js:303 msgid "Starting Transfer" msgstr "Spúšťa sa prenos" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:134 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Prijíma sa „%s“ zo zariadenia %s" #. Action Buttons #: src/service/plugins/share.js:139 src/service/plugins/share.js:310 #: src/service/plugins/share.js:455 src/service/ui/keybindings.js:44 #: src/service/ui/service.js:121 src/service/ui/service.js:300 #: src/service/ui/settings.js:948 src/shell/donotdisturb.js:215 msgid "Cancel" msgstr "Zrušiť" #: src/service/plugins/share.js:172 src/service/plugins/share.js:327 msgid "Transfer Successful" msgstr "Prenos úspešný" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:174 #, javascript-format msgid "Received “%s” from %s" msgstr "Prijatý súbor „%s“ zo zariadenia %s" #: src/service/plugins/share.js:180 msgid "Open Folder" msgstr "Otvoriť priečinok" #: src/service/plugins/share.js:185 msgid "Open File" msgstr "Otvoriť súbor" #: src/service/plugins/share.js:192 src/service/plugins/share.js:335 msgid "Transfer Failed" msgstr "Prenos zlyhal" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:194 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Zlyhalo prijatie súboru „%s“ zo zariadenia %s" #: src/service/plugins/share.js:233 #, 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:305 #, 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:329 #, 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:337 #, 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:408 #, 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:412 msgid "Open when done" msgstr "Otvoriť po dokončení" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:450 #, javascript-format msgid "Send a link to %s" msgstr "Odošle odkaz do zariadenia %s" #: src/service/plugins/share.js:456 msgid "Send" msgstr "Odoslať" #: src/service/plugins/sms.js:12 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:33 msgid "New SMS (URI)" msgstr "Nová SMS (URI)" #: src/service/plugins/sms.js:41 msgid "Reply SMS" msgstr "Odpovedať na SMS" #: src/service/plugins/sms.js:57 msgid "Share SMS" msgstr "Zdieľať SMS" #: src/service/plugins/systemvolume.js:11 msgid "System Volume" msgstr "Systémová hlasitosť" #: src/service/plugins/telephony.js:21 msgid "Mute Call" msgstr "Stíšiť hovor" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:171 msgid "Incoming call" msgstr "Prichádzajúci hovor" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:186 msgid "Ongoing call" msgstr "Odchádzajúci hovor" #. TRANSLATORS: All other phone number types #: src/service/ui/contacts.js:118 src/service/ui/contacts.js:139 #, javascript-format msgid "%s・Other" msgstr "%s・Iné" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:123 #, javascript-format msgid "%s・Fax" msgstr "%s・Fax" #. TRANSLATORS: A work phone number #: src/service/ui/contacts.js:127 #, javascript-format msgid "%s・Work" msgstr "%s・Práca" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:131 #, javascript-format msgid "%s・Mobile" msgstr "%s・Mobil" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:135 #, javascript-format msgid "%s・Home" msgstr "%s・Domov" #: src/service/ui/contacts.js:267 msgid "Select a contact or number" msgstr "Vyberte kontakt alebo číslo" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #: src/service/ui/contacts.js:304 src/service/ui/contacts.js:318 #, javascript-format msgid "Send to %s" msgstr "Odoslať na číslo %s" #. TRANSLATORS: Title of keyboard shortcut dialog #: src/service/ui/keybindings.js:33 msgid "Set Shortcut" msgstr "Nastavenie skratky" #. TRANSLATORS: Button to confirm the new shortcut #: src/service/ui/keybindings.js:47 msgid "Set" msgstr "Nastaviť" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/service/ui/keybindings.js:54 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Zadajte novú skratku pre zmenu akcie %s" #. TRANSLATORS: Keys for cancelling (␛) or resetting (␈) a shortcut #: src/service/ui/keybindings.js:83 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." #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/service/ui/keybindings.js:182 #, javascript-format msgid "%s is already being used" msgstr "Klávesová skratka %s sa už používa" #: src/service/ui/service.js:122 msgid "Connect" msgstr "Pripojiť" #: src/service/ui/service.js:294 msgid "Select a Device" msgstr "Výber zariadenia" #: src/service/ui/service.js:298 msgid "Select" msgstr "Vybrať" #: src/service/ui/settings.js:315 msgid "Panel" msgstr "Panel" #: src/service/ui/settings.js:315 msgid "User Menu" msgstr "Užívateľská ponuka" #: src/service/ui/settings.js:949 msgid "Open" msgstr "Otvoriť" #: src/service/ui/settings.js:1006 src/service/ui/settings.js:1019 msgid "On" msgstr "Zapnuté" #: src/service/ui/settings.js:1006 src/service/ui/settings.js:1019 msgid "Off" msgstr "Vypnuté" #: src/service/ui/settings.js:1135 src/service/ui/settings.js:1201 msgid "Disabled" msgstr "Zakázané" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:93 src/service/ui/messaging.js:126 msgid "Just now" msgstr "Práve teraz" #. TRANSLATORS: Time duration in minutes (eg. 15 minutes) #: src/service/ui/messaging.js:98 src/service/ui/messaging.js:130 #: src/shell/donotdisturb.js:266 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minúta" msgstr[1] "%d minúty" msgstr[2] "%d minút" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:103 #, javascript-format msgid "Yesterday・%s" msgstr "Včera・%s" #: src/service/ui/messaging.js:922 msgid "New Message" msgstr "Nová správa" #. TRANSLATORS: When the battery level is 100% #: src/shell/device.js:82 msgid "Fully Charged" msgstr "Plne nabitá" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:86 #, 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:96 #, 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:104 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (Zostáva %d∶%02d)" #: src/shell/donotdisturb.js:150 src/shell/donotdisturb.js:289 msgid "Do Not Disturb" msgstr "Nevyrušovať" #: src/shell/donotdisturb.js:156 msgid "Silence Mobile Device Notifications" msgstr "Stíšenie oznámení z mobilného zariadenia" #: src/shell/donotdisturb.js:171 msgid "Until you turn off Do Not Disturb" msgstr "Pokiaľ nevypnete funkciu Nerušiť" #. TRANSLATORS: Time until change with time duration #. EXAMPLE: Until 10:00 (2 hours) #: src/shell/donotdisturb.js:184 src/shell/donotdisturb.js:278 #, javascript-format msgid "Until %s (%s)" msgstr "Do %s (%s)" #: src/shell/donotdisturb.js:216 msgid "Done" msgstr "Hotovo" #. TRANSLATORS: Time duration in hours (eg. 2 hours) #: src/shell/donotdisturb.js:263 #, javascript-format msgid "One hour" msgid_plural "%d hours" msgstr[0] "%d hodina" msgstr[1] "%d hodiny" msgstr[2] "%d hodín" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:29 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:33 msgid "Service Unavailable" msgstr "Služba nedostupná" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:37 msgid "Open in Browser" msgstr "Otvoriť v prehliadači" gnome-shell-extension-gsconnect-20/po/sr.po000066400000000000000000000611031341554142200211060ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: org.gnome.Shell.Extensions.GSConnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-11-19 11:14-0800\n" "PO-Revision-Date: 2018-11-04 12:25+0100\n" "Last-Translator: Слободан Терзић \n" "Language-Team: \n" "Language: sr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 2.2\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" #: data/conversation.ui:71 data/conversation.ui:79 msgid "Type a message" msgstr "Унесите поруку" #: data/contacts.ui:51 data/contacts.ui:52 msgid "Type a phone number or name" msgstr "Унеите број телефона или име" #: data/device.ui:68 src/service/plugins/battery.js:12 msgid "Battery" msgstr "Батерија" #: data/device.ui:116 msgid "Clipboard Sync" msgstr "Синхронизација оставе" #: data/device.ui:172 msgid "Media Players" msgstr "Медијски плејери" #: data/device.ui:220 msgid "Mouse & Keyboard" msgstr "Миш и тастатура" #: data/device.ui:268 msgid "Volume Control" msgstr "Јачина звука" #: data/device.ui:308 data/device.ui:1406 msgid "Sharing" msgstr "Дељење" #: data/device.ui:337 data/device.ui:534 data/device.ui:1449 #: src/service/plugins/runcommand.js:18 src/service/plugins/runcommand.js:169 msgid "Commands" msgstr "Наредбе" #: data/device.ui:385 msgid "Name" msgstr "Име" #: data/device.ui:401 data/device.ui:402 msgid "Choose an executable" msgstr "Изаберите извршни фајл" #: data/device.ui:403 msgid "Command Line" msgstr "Командна линија" #: data/device.ui:597 msgid "Share Notifications" msgstr "Дели обавештења" #: data/device.ui:636 msgid "Applications" msgstr "Програми" #: data/device.ui:675 data/device.ui:1492 #: src/service/plugins/notification.js:12 msgid "Notifications" msgstr "Обавештења" #: data/device.ui:705 msgid "Incoming Calls" msgstr "Долазни позиви" #: data/device.ui:755 data/device.ui:900 msgid "Volume" msgstr "Јачина" #: data/device.ui:813 data/device.ui:958 msgid "Pause Media" msgstr "Паузирај медије" #: data/device.ui:852 msgid "Ongoing Calls" msgstr "Текућии позиви" #: data/device.ui:1007 msgid "Mute Microphone" msgstr "Утишај микрофон" #: data/device.ui:1047 data/device.ui:1535 src/service/plugins/telephony.js:12 msgid "Telephony" msgstr "Телефонија" #: data/device.ui:1082 msgid "Action Shortcuts" msgstr "Пречице радњи" #: data/device.ui:1094 data/device.ui:1157 msgid "Reset All…" msgstr "Ресетуј све…" #: data/device.ui:1145 msgid "Command Shortcuts" msgstr "Пречице наредби" #: data/device.ui:1203 msgid "Shortcuts" msgstr "Пречице" #: data/device.ui:1234 msgid "Plugins" msgstr "Прикључци" #: data/device.ui:1295 msgid "Delete" msgstr "Обриши" #: data/device.ui:1320 msgid "Delete this device" msgstr "Обриши овај уређај" #: data/device.ui:1335 msgid "Unpair and remove all settings and files" msgstr "Распари и уклони све поставке и фајлове" #: data/device.ui:1365 data/device.ui:1621 msgid "Advanced" msgstr "Напредно" #: data/device.ui:1578 msgid "Keyboard Shortcuts" msgstr "Пречице тастатуре" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/menus.ui:7 src/service/ui/service.js:115 msgid "Connect to…" msgstr "Повежи се са…" #: data/menus.ui:13 data/settings.ui:881 msgid "Help" msgstr "Помоћ" #. TRANSLATORS: Open the developer's dialog #: data/menus.ui:19 msgid "Debugger" msgstr "Исправљач грешака" #: data/menus.ui:23 msgid "About" msgstr "О програму" #. TRANSLATORS: Change the connection type to Bluetooth #: data/menus.ui:34 #, fuzzy msgid "Switch to Bluetooth" msgstr "Повежи Блутутом" #. TRANSLATORS: Change the connection type to TCP/IP #: data/menus.ui:41 msgid "Switch to LAN" msgstr "" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/menus.ui:48 src/service/ui/settings.js:612 msgid "Encryption Info" msgstr "" #. TRANSLATORS: Send a pair request to the device #: data/menus.ui:53 src/service/device.js:425 msgid "Pair" msgstr "Упари" #. TRANSLATORS: Unpair the device and notify it #: data/menus.ui:59 src/service/device.js:433 msgid "Unpair" msgstr "Распари" #. TRANSLATORS: Send clipboard content to device #: data/menus.ui:71 msgid "To Device" msgstr "На уређај" #. TRANSLATORS: Receive clipboard content from the device #: data/menus.ui:77 msgid "From Device" msgstr "Са уређаја" #. TRANSLATORS: Don't change the system volume #: data/menus.ui:89 data/menus.ui:115 msgid "Nothing" msgstr "Ништа" #. TRANSLATORS: Lower the system volume #: data/menus.ui:96 data/menus.ui:122 msgid "Lower" msgstr "Утишај" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the phone ringer #: data/menus.ui:103 data/menus.ui:129 src/service/plugins/telephony.js:176 msgid "Mute" msgstr "Утишај" #: data/messaging.ui:12 src/service/plugins/sms.js:25 #: src/service/ui/messaging.js:871 msgid "Messaging" msgstr "Поруке" #: data/messaging.ui:110 #, fuzzy msgid "Select a conversation" msgstr "Додајте особе да започнете разговор" #: data/messaging.ui:188 msgid "Device is disconnected" msgstr "Уређај није повезан" #: data/settings.ui:333 msgid "Appearance" msgstr "Изглед" #: data/settings.ui:383 msgid "Display Mode" msgstr "Режим приказа" #: data/settings.ui:425 msgid "Service" msgstr "Сервис" #: data/settings.ui:475 msgid "Discoverable" msgstr "Откривање је омогућено" #: data/settings.ui:528 msgid "Restart Service" msgstr "Поново покрени сервис" #: data/settings.ui:581 data/settings.ui:1015 src/service/device.js:417 msgid "Settings" msgstr "Подешавање" #: data/settings.ui:639 msgid "Remote Filesystems" msgstr "Удаљени системи фајлова" #: data/settings.ui:675 msgid "Sound Effects" msgstr "Звучни ефекти" #: data/settings.ui:712 msgid "Extended Keyboard Support" msgstr "Проширена подршка за тастатуру" #: data/settings.ui:749 msgid "Desktop Contacts" msgstr "Контакти са десктопа" #: data/settings.ui:786 msgid "Files Integration" msgstr "Уграђивање у Фајлове" #: data/settings.ui:813 msgid "Additional Features" msgstr "Додатне могућности" #: data/settings.ui:903 msgid "Browser Add-Ons" msgstr "Додаци за прегледаче" # Leaving the name in original form for About dialog. #: data/settings.ui:921 msgid "KDE Connect" msgstr "KDE Connect" #: data/settings.ui:956 data/settings.ui:1058 msgid "Other" msgstr "Друго" #. TRANSLATORS: No devices are known or available #: data/settings.ui:1106 webextension/gettext.js:35 msgid "No Device Found" msgstr "Нема нађених уређаја" #: data/settings.ui:1142 msgid "Refresh" msgstr "Освежи" #. Service Menu #: src/extension.js:79 src/extension.js:259 msgid "Mobile Devices" msgstr "Мобилни уређаји" #: src/extension.js:105 msgid "Mobile Settings" msgstr "Подешавање" # Leaving the name in original form for About dialog. #. TRANSLATORS: Extension name #: src/extension.js:196 src/extension.js:311 src/service/daemon.js:95 #: src/service/daemon.js:413 webextension/gettext.js:27 msgid "GSConnect" msgstr "GSConnect" #. TRANSLATORS: %d is the number of devices connected #: src/extension.js:257 #, fuzzy, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d је повезан" msgstr[1] "%d је повезан" msgstr[2] "%d је повезан" #. TRANSLATORS: Top-level context menu item for GSConnect #: src/nautilus-gsconnect.py:112 webextension/gettext.js:31 msgid "Send To Mobile Device" msgstr "Пошаљи на мобилни уређај" #: src/service/daemon.js:406 msgid "A complete KDE Connect implementation for GNOME" msgstr "Потпуна имплементација КДЕ Конекта за Гном" #. TRANSLATORS: eg. 'Translator Name ' #: src/service/daemon.js:415 msgid "translator-credits" msgstr "Слободан Терзић (githzerai06@gmail.com)" #: src/service/daemon.js:437 msgid "Report" msgstr "Пријави" #: src/service/daemon.js:567 msgid "Authentication Failure" msgstr "Грешка аутентификацје" #: src/service/daemon.js:576 msgid "Network Error" msgstr "Грешка мреже" #: src/service/daemon.js:577 src/service/daemon.js:587 msgid "Click for help troubleshooting" msgstr "Кликните за помоћ у отклањању" #: src/service/daemon.js:586 msgid "PulseAudio Error" msgstr "Грешка Пулсаудија" #: src/service/daemon.js:596 msgid "Discovery Disabled" msgstr "Откривање је онемогућено" #: src/service/daemon.js:597 msgid "" "Discovery has been disabled due to the number of devices on this network." msgstr "Откривање је онемогућено услед броја уређаја у овој мрежи." #: src/service/daemon.js:599 src/service/daemon.js:609 msgid "Click to open preferences" msgstr "Кликните да отворите поставке" #: src/service/daemon.js:608 msgid "Additional Software Required" msgstr "Неопходан је додатан софтвер" #: src/service/daemon.js:617 #, javascript-format msgid "%s Plugin Failed To Load" msgstr "Неуспело учитавање пркључка %s" #: src/service/daemon.js:618 src/service/daemon.js:635 msgid "Click for more information" msgstr "Кликните за више детаља" #: src/service/daemon.js:633 msgid "Wayland Not Supported" msgstr "Вејланд није подржан" #: src/service/daemon.js:634 msgid "Remote input not supported on Wayland" msgstr "Унос на даљину није подржан под Вејландом" # Leaving the name in original form for About dialog. #. Create an urgent notification #: src/service/daemon.js:658 #, javascript-format msgid "GSConnect: %s" msgstr "GSConnect: %s" #. TRANSLATORS: Share URL by SMS #: src/service/daemon.js:808 src/service/plugins/sms.js:49 #: webextension/gettext.js:39 msgid "Send SMS" msgstr "Пошаљи СМС" #: src/service/daemon.js:812 msgid "Dial Number" msgstr "Бирај број" #: src/service/device.js:166 msgid "Not available" msgstr "Није доступно" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:170 #, javascript-format msgid "Bluetooth device at %s" msgstr "Блутут уређај на %s" #. TRANSLATORS: Label for TLS Certificate fingerprint #. #. Example: #. #. Google Pixel Fingerprint: #. 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 #: src/service/device.js:188 src/service/device.js:190 #, javascript-format msgid "%s Fingerprint:" msgstr "Отисак %s:" #: src/service/device.js:240 msgid "Laptop" msgstr "Лаптоп" #: src/service/device.js:242 msgid "Smartphone" msgstr "Паметни телефон" #: src/service/device.js:244 msgid "Tablet" msgstr "Таблет" #: src/service/device.js:246 msgid "Desktop" msgstr "Радна површ" #: src/service/device.js:409 msgid "Reconnect" msgstr "Поново повежи" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:606 #, javascript-format msgid "Pair Request from %s" msgstr "Захтев за упаривање од %s" #: src/service/device.js:613 msgid "Reject" msgstr "Одбиј" #: src/service/device.js:618 msgid "Accept" msgstr "Прихвати" #: src/service/plugins/battery.js:194 src/service/plugins/findmyphone.js:18 #, fuzzy msgid "Ring" msgstr "Лоцирај" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:203 #, javascript-format msgid "%s: Battery is low" msgstr "%s: батерија је при крају" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:205 #, javascript-format msgid "%d%% remaining" msgstr "%d%% преостаје" #: src/service/plugins/clipboard.js:11 msgid "Clipboard" msgstr "Остава" #: src/service/plugins/clipboard.js:17 msgid "Clipboard Push" msgstr "Слање у оставу" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Pull" msgstr "Довлачење из оставе" #: src/service/plugins/contacts.js:12 msgid "Contacts" msgstr "Контакти" #: src/service/plugins/findmyphone.js:12 msgid "Find My Phone" msgstr "Нађи ми телефон" #: src/service/plugins/findmyphone.js:65 msgid "Locate Device" msgstr "Лоцирање уређаја" #: src/service/plugins/findmyphone.js:66 #, javascript-format msgid "%s asked to locate this device" msgstr "%s тражи овај уређај" #: src/service/plugins/findmyphone.js:74 msgid "Found" msgstr "Нађен" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Подлога за миша" #: src/service/plugins/mousepad.js:28 src/service/plugins/mousepad.js:578 msgid "Keyboard" msgstr "Тастатура" #: src/service/plugins/mousepad.js:595 msgid "Keyboard not ready" msgstr "Тастатура није спремна" #: src/service/plugins/mpris.js:10 msgid "MPRIS" msgstr "МПРИС" #: src/service/plugins/notification.js:25 msgid "Cancel Notification" msgstr "Откажи обавештење" #: src/service/plugins/notification.js:33 msgid "Close Notification" msgstr "Затвори обавештење" #: src/service/plugins/notification.js:41 msgid "Reply Notification" msgstr "Обавештење о оддговору" #: src/service/plugins/notification.js:49 msgid "Send Notification" msgstr "Пошаљи обавештење" #: src/service/plugins/ping.js:11 src/service/plugins/ping.js:17 #: src/service/plugins/ping.js:46 msgid "Ping" msgstr "Пинг" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:53 #, javascript-format msgid "Ping: %s" msgstr "Пинг: %s" #: src/service/plugins/runcommand.js:12 msgid "Run Commands" msgstr "Извршавање нареби" #: src/service/plugins/sftp.js:12 msgid "SFTP" msgstr "СФТП" #: src/service/plugins/sftp.js:18 msgid "Mount" msgstr "Монтирај" #: src/service/plugins/sftp.js:26 msgid "Unmount" msgstr "Демонтирај" #: src/service/plugins/sftp.js:170 msgid "All files" msgstr "Сви фајлови" #: src/service/plugins/sftp.js:171 msgid "Camera pictures" msgstr "Слике са камере" #: src/service/plugins/sftp.js:333 msgid "Files" msgstr "Фајлови" #: src/service/plugins/share.js:12 src/service/plugins/share.js:18 msgid "Share" msgstr "Дељење" #: src/service/plugins/share.js:26 msgid "Share File" msgstr "Дели фајл" #: src/service/plugins/share.js:34 msgid "Share Text" msgstr "Дели текст" #: src/service/plugins/share.js:42 src/service/ui/messaging.js:903 #: src/service/ui/messaging.js:911 msgid "Share Link" msgstr "Подели везу" #: src/service/plugins/share.js:131 src/service/plugins/share.js:282 msgid "Starting Transfer" msgstr "Започињем пренос" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:133 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Примам „%s“ од %s" #. Action Buttons #: src/service/plugins/share.js:138 src/service/plugins/share.js:289 #: src/service/plugins/share.js:407 src/service/ui/keybindings.js:44 #: src/service/ui/service.js:121 src/service/ui/service.js:300 #: src/service/ui/settings.js:873 src/shell/donotdisturb.js:215 msgid "Cancel" msgstr "Откажи" #: src/service/plugins/share.js:149 src/service/plugins/share.js:303 msgid "Transfer Successful" msgstr "Успешан пренос" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:151 #, javascript-format msgid "Received “%s” from %s" msgstr "Примих „%s“ од %s" #: src/service/plugins/share.js:157 msgid "Open Folder" msgstr "Отвори фасциклу" #: src/service/plugins/share.js:162 msgid "Open File" msgstr "Отвори фајл" #: src/service/plugins/share.js:169 src/service/plugins/share.js:311 msgid "Transfer Failed" msgstr "Пренос није успео" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:171 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Неуспешан пријем „%s“ од %s" #: src/service/plugins/share.js:211 #, javascript-format msgid "Text Shared By %s" msgstr "Дељени текст од %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:284 #, javascript-format msgid "Sending “%s” to %s" msgstr "Слање „%s“ за %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:305 #, 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:313 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "Неуспело слање „%s“ на %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:384 #, javascript-format msgid "Send files to %s" msgstr "Пошаљи фајлове на %s" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:402 #, javascript-format msgid "Send a link to %s" msgstr "Пошаљи везе на %s" #: src/service/plugins/share.js:408 msgid "Send" msgstr "Пошаљи" #: src/service/plugins/sms.js:12 msgid "SMS" msgstr "СМС" #: src/service/plugins/sms.js:33 msgid "New SMS (URI)" msgstr "Нови СМС (УРИ)" #: src/service/plugins/sms.js:41 msgid "Reply SMS" msgstr "Одговори на СМС" #: src/service/plugins/sms.js:57 msgid "Share SMS" msgstr "Дели СМС" #: src/service/plugins/systemvolume.js:11 msgid "System Volume" msgstr "Системска јачина" #: src/service/plugins/telephony.js:21 msgid "Mute Call" msgstr "Утишај позив" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #: src/service/plugins/telephony.js:155 src/service/ui/contacts.js:96 #: src/service/ui/contacts.js:350 msgid "Unknown Contact" msgstr "Непознат контакт" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:172 msgid "Incoming call" msgstr "Долазни позив" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:187 msgid "Ongoing call" msgstr "Текући позив" #. TRANSLATORS: All other phone number types #: src/service/ui/contacts.js:58 src/service/ui/contacts.js:79 #, fuzzy, javascript-format msgid "%s・Other" msgstr "%s・Друго" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:63 #, javascript-format msgid "%s・Fax" msgstr "%s・Факс" #. TRANSLATORS: A work phone number #: src/service/ui/contacts.js:67 #, fuzzy, javascript-format msgid "%s・Work" msgstr "%s・Посао" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:71 #, fuzzy, javascript-format msgid "%s・Mobile" msgstr "%s・Мобилни" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:75 #, fuzzy, javascript-format msgid "%s・Home" msgstr "%s・Кућни" #: src/service/ui/contacts.js:203 msgid "Select a contact or number" msgstr "" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #: src/service/ui/contacts.js:239 src/service/ui/contacts.js:253 #, javascript-format msgid "Send to %s" msgstr "Пошаљи на %s" #. TRANSLATORS: Title of keyboard shortcut dialog #: src/service/ui/keybindings.js:33 msgid "Set Shortcut" msgstr "Постави пречицу" #. TRANSLATORS: Button to confirm the new shortcut #: src/service/ui/keybindings.js:47 msgid "Set" msgstr "Постави" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/service/ui/keybindings.js:54 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Унесите нову пречицу да замените %s" #. TRANSLATORS: Keys for cancelling (␛) or resetting (␈) a shortcut #: src/service/ui/keybindings.js:83 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "" "Притисните Есц да откажете или Повратник да ресетујете пречицу тастатуре." #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/service/ui/keybindings.js:182 #, javascript-format msgid "%s is already being used" msgstr "%s је спреман за употребу" #: src/service/ui/service.js:122 msgid "Connect" msgstr "Повежи" #: src/service/ui/service.js:294 msgid "Select a Device" msgstr "Изаберите уређај" #: src/service/ui/service.js:298 msgid "Select" msgstr "Изаберите" #: src/service/ui/settings.js:298 msgid "Panel" msgstr "Панел" #: src/service/ui/settings.js:298 msgid "User Menu" msgstr "Кориснички мени" #: src/service/ui/settings.js:874 msgid "Open" msgstr "Отвори" #: src/service/ui/settings.js:931 src/service/ui/settings.js:944 msgid "On" msgstr "Укључен" #: src/service/ui/settings.js:931 src/service/ui/settings.js:944 msgid "Off" msgstr "Искључен" #: src/service/ui/settings.js:1065 src/service/ui/settings.js:1131 msgid "Disabled" msgstr "Онемогућен" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:93 src/service/ui/messaging.js:126 msgid "Just now" msgstr "Управо сада" #. TRANSLATORS: Time duration in minutes (eg. 15 minutes) #: src/service/ui/messaging.js:98 src/service/ui/messaging.js:130 #: src/shell/donotdisturb.js:266 #, fuzzy, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d минута" msgstr[1] "%d минута" msgstr[2] "%d минута" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:103 #, javascript-format msgid "Yesterday・%s" msgstr "Јуче・%s" #: src/service/ui/messaging.js:920 msgid "New Message" msgstr "Нова порука" #. TRANSLATORS: When the battery level is 100% #: src/shell/device.js:86 msgid "Fully Charged" msgstr "Потпуно пуна" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:90 #, javascript-format msgid "%d%% (Estimating…)" msgstr "%d%% (Процењујем…)" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:100 #, 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:108 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d преостаје)" #: src/shell/donotdisturb.js:150 src/shell/donotdisturb.js:289 msgid "Do Not Disturb" msgstr "Не узнемиравај" #: src/shell/donotdisturb.js:156 msgid "Silence Mobile Device Notifications" msgstr "Утишај обавештења мобилиних уређаја" #: src/shell/donotdisturb.js:171 msgid "Until you turn off Do Not Disturb" msgstr "Док не искључим Не узнемиравај" #. TRANSLATORS: Time until change with time duration #. EXAMPLE: Until 10:00 (2 hours) #: src/shell/donotdisturb.js:184 src/shell/donotdisturb.js:278 #, javascript-format msgid "Until %s (%s)" msgstr "До %s (%s)" #: src/shell/donotdisturb.js:216 msgid "Done" msgstr "Готово" #. TRANSLATORS: Time duration in hours (eg. 2 hours) #: src/shell/donotdisturb.js:263 #, javascript-format msgid "One hour" msgid_plural "%d hours" msgstr[0] "%d час" msgstr[1] "%d часа" msgstr[2] "%d часова" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:29 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "Дели везе ГСКонектом, директно у преглдач или путем СМС-а." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:33 msgid "Service Unavailable" msgstr "Сервис није доступан" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:37 msgid "Open in Browser" msgstr "Отвори у прегледачу" gnome-shell-extension-gsconnect-20/po/sr@latin.po000066400000000000000000000545041341554142200222450ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: org.gnome.Shell.Extensions.GSConnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-11-19 11:14-0800\n" "PO-Revision-Date: 2018-11-05 11:00+0100\n" "Last-Translator: Slobodan Terzić \n" "Language-Team: \n" "Language: sr@latin\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 2.2\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" #: data/conversation.ui:71 data/conversation.ui:79 msgid "Type a message" msgstr "Unesite poruku" #: data/contacts.ui:51 data/contacts.ui:52 msgid "Type a phone number or name" msgstr "Uneite broj telefona ili ime" #: data/device.ui:68 src/service/plugins/battery.js:12 msgid "Battery" msgstr "Baterija" #: data/device.ui:116 msgid "Clipboard Sync" msgstr "Sinhronizacija ostave" #: data/device.ui:172 msgid "Media Players" msgstr "Medijski plejeri" #: data/device.ui:220 msgid "Mouse & Keyboard" msgstr "Miš i tastatura" #: data/device.ui:268 msgid "Volume Control" msgstr "Jačina zvuka" #: data/device.ui:308 data/device.ui:1406 msgid "Sharing" msgstr "Deljenje" #: data/device.ui:337 data/device.ui:534 data/device.ui:1449 #: src/service/plugins/runcommand.js:18 src/service/plugins/runcommand.js:169 msgid "Commands" msgstr "Naredbe" #: data/device.ui:385 msgid "Name" msgstr "Ime" #: data/device.ui:401 data/device.ui:402 msgid "Choose an executable" msgstr "Izaberite izvršni fajl" #: data/device.ui:403 msgid "Command Line" msgstr "Komandna linija" #: data/device.ui:597 msgid "Share Notifications" msgstr "Deli obaveštenja" #: data/device.ui:636 msgid "Applications" msgstr "Programi" #: data/device.ui:675 data/device.ui:1492 #: src/service/plugins/notification.js:12 msgid "Notifications" msgstr "Obaveštenja" #: data/device.ui:705 msgid "Incoming Calls" msgstr "Dolazni pozivi" #: data/device.ui:755 data/device.ui:900 msgid "Volume" msgstr "Jačina" #: data/device.ui:813 data/device.ui:958 msgid "Pause Media" msgstr "Pauziraj medije" #: data/device.ui:852 msgid "Ongoing Calls" msgstr "Tekućii pozivi" #: data/device.ui:1007 msgid "Mute Microphone" msgstr "Utišaj mikrofon" #: data/device.ui:1047 data/device.ui:1535 src/service/plugins/telephony.js:12 msgid "Telephony" msgstr "Telefonija" #: data/device.ui:1082 msgid "Action Shortcuts" msgstr "Prečice radnji" #: data/device.ui:1094 data/device.ui:1157 msgid "Reset All…" msgstr "Resetuj sve…" #: data/device.ui:1145 msgid "Command Shortcuts" msgstr "Prečice naredbi" #: data/device.ui:1203 msgid "Shortcuts" msgstr "Prečice" #: data/device.ui:1234 msgid "Plugins" msgstr "Priključci" #: data/device.ui:1295 msgid "Delete" msgstr "Obriši" #: data/device.ui:1320 msgid "Delete this device" msgstr "Obriši ovaj uređaj" #: data/device.ui:1335 msgid "Unpair and remove all settings and files" msgstr "Raspari i ukloni sve postavke i fajlove" #: data/device.ui:1365 data/device.ui:1621 msgid "Advanced" msgstr "Napredno" #: data/device.ui:1578 msgid "Keyboard Shortcuts" msgstr "Prečice tastature" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/menus.ui:7 src/service/ui/service.js:115 msgid "Connect to…" msgstr "Poveži se sa…" #: data/menus.ui:13 data/settings.ui:881 msgid "Help" msgstr "Pomoć" #. TRANSLATORS: Open the developer's dialog #: data/menus.ui:19 msgid "Debugger" msgstr "Ispravljač grešaka" #: data/menus.ui:23 msgid "About" msgstr "O programu" #. TRANSLATORS: Change the connection type to Bluetooth #: data/menus.ui:34 #, fuzzy msgid "Switch to Bluetooth" msgstr "Poveži Blututom" #. TRANSLATORS: Change the connection type to TCP/IP #: data/menus.ui:41 msgid "Switch to LAN" msgstr "" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/menus.ui:48 src/service/ui/settings.js:612 msgid "Encryption Info" msgstr "" #. TRANSLATORS: Send a pair request to the device #: data/menus.ui:53 src/service/device.js:425 msgid "Pair" msgstr "Upari" #. TRANSLATORS: Unpair the device and notify it #: data/menus.ui:59 src/service/device.js:433 msgid "Unpair" msgstr "Raspari" #. TRANSLATORS: Send clipboard content to device #: data/menus.ui:71 msgid "To Device" msgstr "Na uređaj" #. TRANSLATORS: Receive clipboard content from the device #: data/menus.ui:77 msgid "From Device" msgstr "Sa uređaja" #. TRANSLATORS: Don't change the system volume #: data/menus.ui:89 data/menus.ui:115 msgid "Nothing" msgstr "Ništa" #. TRANSLATORS: Lower the system volume #: data/menus.ui:96 data/menus.ui:122 msgid "Lower" msgstr "Utišaj" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the phone ringer #: data/menus.ui:103 data/menus.ui:129 src/service/plugins/telephony.js:176 msgid "Mute" msgstr "Utišaj" #: data/messaging.ui:12 src/service/plugins/sms.js:25 #: src/service/ui/messaging.js:871 msgid "Messaging" msgstr "Poruke" #: data/messaging.ui:110 #, fuzzy msgid "Select a conversation" msgstr "Dodajte osobe da započnete razgovor" #: data/messaging.ui:188 msgid "Device is disconnected" msgstr "Uređaj nije povezan" #: data/settings.ui:333 msgid "Appearance" msgstr "Izgled" #: data/settings.ui:383 msgid "Display Mode" msgstr "Režim prikaza" #: data/settings.ui:425 msgid "Service" msgstr "Servis" #: data/settings.ui:475 msgid "Discoverable" msgstr "Otkrivanje je omogućeno" #: data/settings.ui:528 msgid "Restart Service" msgstr "Ponovo pokreni servis" #: data/settings.ui:581 data/settings.ui:1015 src/service/device.js:417 msgid "Settings" msgstr "Podešavanje" #: data/settings.ui:639 msgid "Remote Filesystems" msgstr "Udaljeni sistemi fajlova" #: data/settings.ui:675 msgid "Sound Effects" msgstr "Zvučni efekti" #: data/settings.ui:712 msgid "Extended Keyboard Support" msgstr "Proširena podrška za tastaturu" #: data/settings.ui:749 msgid "Desktop Contacts" msgstr "Kontakti sa desktopa" #: data/settings.ui:786 msgid "Files Integration" msgstr "Ugrađivanje u Fajlove" #: data/settings.ui:813 msgid "Additional Features" msgstr "Dodatne mogućnosti" #: data/settings.ui:903 msgid "Browser Add-Ons" msgstr "Dodaci za pregledače" # Leaving the name in original form for About dialog. #: data/settings.ui:921 msgid "KDE Connect" msgstr "KDE Connect" #: data/settings.ui:956 data/settings.ui:1058 msgid "Other" msgstr "Drugo" #. TRANSLATORS: No devices are known or available #: data/settings.ui:1106 webextension/gettext.js:35 msgid "No Device Found" msgstr "Nema nađenih uređaja" #: data/settings.ui:1142 msgid "Refresh" msgstr "Osveži" #. Service Menu #: src/extension.js:79 src/extension.js:259 msgid "Mobile Devices" msgstr "Mobilni uređaji" #: src/extension.js:105 msgid "Mobile Settings" msgstr "Podešavanje" # Leaving the name in original form for About dialog. #. TRANSLATORS: Extension name #: src/extension.js:196 src/extension.js:311 src/service/daemon.js:95 #: src/service/daemon.js:413 webextension/gettext.js:27 msgid "GSConnect" msgstr "GSConnect" #. TRANSLATORS: %d is the number of devices connected #: src/extension.js:257 #, fuzzy, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d je povezan" msgstr[1] "%d je povezan" msgstr[2] "%d je povezan" #. TRANSLATORS: Top-level context menu item for GSConnect #: src/nautilus-gsconnect.py:112 webextension/gettext.js:31 msgid "Send To Mobile Device" msgstr "Pošalji na mobilni uređaj" #: src/service/daemon.js:406 msgid "A complete KDE Connect implementation for GNOME" msgstr "Potpuna implementacija KDE Konekta za Gnom" #. TRANSLATORS: eg. 'Translator Name ' #: src/service/daemon.js:415 msgid "translator-credits" msgstr "Slobodan Terzić (githzerai06@gmail.com)" #: src/service/daemon.js:437 msgid "Report" msgstr "Prijavi" #: src/service/daemon.js:567 msgid "Authentication Failure" msgstr "Greška autentifikacje" #: src/service/daemon.js:576 msgid "Network Error" msgstr "Greška mreže" #: src/service/daemon.js:577 src/service/daemon.js:587 msgid "Click for help troubleshooting" msgstr "Kliknite za pomoć u otklanjanju" #: src/service/daemon.js:586 msgid "PulseAudio Error" msgstr "Greška Pulsaudija" #: src/service/daemon.js:596 msgid "Discovery Disabled" msgstr "Otkrivanje je onemogućeno" #: src/service/daemon.js:597 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/daemon.js:599 src/service/daemon.js:609 msgid "Click to open preferences" msgstr "Kliknite da otvorite postavke" #: src/service/daemon.js:608 msgid "Additional Software Required" msgstr "Neophodan je dodatan softver" #: src/service/daemon.js:617 #, javascript-format msgid "%s Plugin Failed To Load" msgstr "Neuspelo učitavanje prključka %s" #: src/service/daemon.js:618 src/service/daemon.js:635 msgid "Click for more information" msgstr "Kliknite za više detalja" #: src/service/daemon.js:633 msgid "Wayland Not Supported" msgstr "Vejland nije podržan" #: src/service/daemon.js:634 msgid "Remote input not supported on Wayland" msgstr "Unos na daljinu nije podržan pod Vejlandom" # Leaving the name in original form for About dialog. #. Create an urgent notification #: src/service/daemon.js:658 #, javascript-format msgid "GSConnect: %s" msgstr "GSConnect: %s" #. TRANSLATORS: Share URL by SMS #: src/service/daemon.js:808 src/service/plugins/sms.js:49 #: webextension/gettext.js:39 msgid "Send SMS" msgstr "Pošalji SMS" #: src/service/daemon.js:812 msgid "Dial Number" msgstr "Biraj broj" #: src/service/device.js:166 msgid "Not available" msgstr "Nije dostupno" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:170 #, javascript-format msgid "Bluetooth device at %s" msgstr "Blutut uređaj na %s" #. TRANSLATORS: Label for TLS Certificate fingerprint #. #. Example: #. #. Google Pixel Fingerprint: #. 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 #: src/service/device.js:188 src/service/device.js:190 #, javascript-format msgid "%s Fingerprint:" msgstr "Otisak %s:" #: src/service/device.js:240 msgid "Laptop" msgstr "Laptop" #: src/service/device.js:242 msgid "Smartphone" msgstr "Pametni telefon" #: src/service/device.js:244 msgid "Tablet" msgstr "Tablet" #: src/service/device.js:246 msgid "Desktop" msgstr "Radna površ" #: src/service/device.js:409 msgid "Reconnect" msgstr "Ponovo poveži" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:606 #, javascript-format msgid "Pair Request from %s" msgstr "Zahtev za uparivanje od %s" #: src/service/device.js:613 msgid "Reject" msgstr "Odbij" #: src/service/device.js:618 msgid "Accept" msgstr "Prihvati" #: src/service/plugins/battery.js:194 src/service/plugins/findmyphone.js:18 #, fuzzy msgid "Ring" msgstr "Lociraj" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:203 #, javascript-format msgid "%s: Battery is low" msgstr "%s: baterija je pri kraju" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:205 #, javascript-format msgid "%d%% remaining" msgstr "%d%% preostaje" #: src/service/plugins/clipboard.js:11 msgid "Clipboard" msgstr "Ostava" #: src/service/plugins/clipboard.js:17 msgid "Clipboard Push" msgstr "Slanje u ostavu" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Pull" msgstr "Dovlačenje iz ostave" #: src/service/plugins/contacts.js:12 msgid "Contacts" msgstr "Kontakti" #: src/service/plugins/findmyphone.js:12 msgid "Find My Phone" msgstr "Nađi mi telefon" #: src/service/plugins/findmyphone.js:65 msgid "Locate Device" msgstr "Lociranje uređaja" #: src/service/plugins/findmyphone.js:66 #, javascript-format msgid "%s asked to locate this device" msgstr "%s traži ovaj uređaj" #: src/service/plugins/findmyphone.js:74 msgid "Found" msgstr "Nađen" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Podloga za miša" #: src/service/plugins/mousepad.js:28 src/service/plugins/mousepad.js:578 msgid "Keyboard" msgstr "Tastatura" #: src/service/plugins/mousepad.js:595 msgid "Keyboard not ready" msgstr "Tastatura nije spremna" #: src/service/plugins/mpris.js:10 msgid "MPRIS" msgstr "MPRIS" #: src/service/plugins/notification.js:25 msgid "Cancel Notification" msgstr "Otkaži obaveštenje" #: src/service/plugins/notification.js:33 msgid "Close Notification" msgstr "Zatvori obaveštenje" #: src/service/plugins/notification.js:41 msgid "Reply Notification" msgstr "Obaveštenje o oddgovoru" #: src/service/plugins/notification.js:49 msgid "Send Notification" msgstr "Pošalji obaveštenje" #: src/service/plugins/ping.js:11 src/service/plugins/ping.js:17 #: src/service/plugins/ping.js:46 msgid "Ping" 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:53 #, javascript-format msgid "Ping: %s" msgstr "Ping: %s" #: src/service/plugins/runcommand.js:12 msgid "Run Commands" msgstr "Izvršavanje narebi" #: src/service/plugins/sftp.js:12 msgid "SFTP" msgstr "SFTP" #: src/service/plugins/sftp.js:18 msgid "Mount" msgstr "Montiraj" #: src/service/plugins/sftp.js:26 msgid "Unmount" msgstr "Demontiraj" #: src/service/plugins/sftp.js:170 msgid "All files" msgstr "Svi fajlovi" #: src/service/plugins/sftp.js:171 msgid "Camera pictures" msgstr "Slike sa kamere" #: src/service/plugins/sftp.js:333 msgid "Files" msgstr "Fajlovi" #: src/service/plugins/share.js:12 src/service/plugins/share.js:18 msgid "Share" msgstr "Deljenje" #: src/service/plugins/share.js:26 msgid "Share File" msgstr "Deli fajl" #: src/service/plugins/share.js:34 msgid "Share Text" msgstr "Deli tekst" #: src/service/plugins/share.js:42 src/service/ui/messaging.js:903 #: src/service/ui/messaging.js:911 msgid "Share Link" msgstr "Podeli vezu" #: src/service/plugins/share.js:131 src/service/plugins/share.js:282 msgid "Starting Transfer" msgstr "Započinjem prenos" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:133 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Primam „%s“ od %s" #. Action Buttons #: src/service/plugins/share.js:138 src/service/plugins/share.js:289 #: src/service/plugins/share.js:407 src/service/ui/keybindings.js:44 #: src/service/ui/service.js:121 src/service/ui/service.js:300 #: src/service/ui/settings.js:873 src/shell/donotdisturb.js:215 msgid "Cancel" msgstr "Otkaži" #: src/service/plugins/share.js:149 src/service/plugins/share.js:303 msgid "Transfer Successful" msgstr "Uspešan prenos" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:151 #, javascript-format msgid "Received “%s” from %s" msgstr "Primih „%s“ od %s" #: src/service/plugins/share.js:157 msgid "Open Folder" msgstr "Otvori fasciklu" #: src/service/plugins/share.js:162 msgid "Open File" msgstr "Otvori fajl" #: src/service/plugins/share.js:169 src/service/plugins/share.js:311 msgid "Transfer Failed" msgstr "Prenos nije uspeo" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:171 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Neuspešan prijem „%s“ od %s" #: src/service/plugins/share.js:211 #, javascript-format msgid "Text Shared By %s" msgstr "Deljeni tekst od %s" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:284 #, javascript-format msgid "Sending “%s” to %s" msgstr "Slanje „%s“ za %s" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:305 #, javascript-format msgid "Sent “%s” to %s" msgstr "Poslah „%s“ za %s" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:313 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "Neuspelo slanje „%s“ na %s" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:384 #, javascript-format msgid "Send files to %s" msgstr "Pošalji fajlove na %s" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:402 #, javascript-format msgid "Send a link to %s" msgstr "Pošalji veze na %s" #: src/service/plugins/share.js:408 msgid "Send" msgstr "Pošalji" #: src/service/plugins/sms.js:12 msgid "SMS" msgstr "SMS" #: src/service/plugins/sms.js:33 msgid "New SMS (URI)" msgstr "Novi SMS (URI)" #: src/service/plugins/sms.js:41 msgid "Reply SMS" msgstr "Odgovori na SMS" #: src/service/plugins/sms.js:57 msgid "Share SMS" msgstr "Deli SMS" #: src/service/plugins/systemvolume.js:11 msgid "System Volume" msgstr "Sistemska jačina" #: src/service/plugins/telephony.js:21 msgid "Mute Call" msgstr "Utišaj poziv" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #: src/service/plugins/telephony.js:155 src/service/ui/contacts.js:96 #: src/service/ui/contacts.js:350 msgid "Unknown Contact" msgstr "Nepoznat kontakt" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:172 msgid "Incoming call" msgstr "Dolazni poziv" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:187 msgid "Ongoing call" msgstr "Tekući poziv" #. TRANSLATORS: All other phone number types #: src/service/ui/contacts.js:58 src/service/ui/contacts.js:79 #, fuzzy, javascript-format msgid "%s・Other" msgstr "%s・Drugo" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:63 #, javascript-format msgid "%s・Fax" msgstr "%s・Faks" #. TRANSLATORS: A work phone number #: src/service/ui/contacts.js:67 #, fuzzy, javascript-format msgid "%s・Work" msgstr "%s・Posao" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:71 #, fuzzy, javascript-format msgid "%s・Mobile" msgstr "%s・Mobilni" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:75 #, fuzzy, javascript-format msgid "%s・Home" msgstr "%s・Kućni" #: src/service/ui/contacts.js:203 msgid "Select a contact or number" msgstr "" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #: src/service/ui/contacts.js:239 src/service/ui/contacts.js:253 #, javascript-format msgid "Send to %s" msgstr "Pošalji na %s" #. TRANSLATORS: Title of keyboard shortcut dialog #: src/service/ui/keybindings.js:33 msgid "Set Shortcut" msgstr "Postavi prečicu" #. TRANSLATORS: Button to confirm the new shortcut #: src/service/ui/keybindings.js:47 msgid "Set" msgstr "Postavi" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/service/ui/keybindings.js:54 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Unesite novu prečicu da zamenite %s" #. TRANSLATORS: Keys for cancelling (␛) or resetting (␈) a shortcut #: src/service/ui/keybindings.js:83 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "" "Pritisnite Esc da otkažete ili Povratnik da resetujete prečicu tastature." #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/service/ui/keybindings.js:182 #, javascript-format msgid "%s is already being used" msgstr "%s je spreman za upotrebu" #: src/service/ui/service.js:122 msgid "Connect" msgstr "Poveži" #: src/service/ui/service.js:294 msgid "Select a Device" msgstr "Izaberite uređaj" #: src/service/ui/service.js:298 msgid "Select" msgstr "Izaberite" #: src/service/ui/settings.js:298 msgid "Panel" msgstr "Panel" #: src/service/ui/settings.js:298 msgid "User Menu" msgstr "Korisnički meni" #: src/service/ui/settings.js:874 msgid "Open" msgstr "Otvori" #: src/service/ui/settings.js:931 src/service/ui/settings.js:944 msgid "On" msgstr "Uključen" #: src/service/ui/settings.js:931 src/service/ui/settings.js:944 msgid "Off" msgstr "Isključen" #: src/service/ui/settings.js:1065 src/service/ui/settings.js:1131 msgid "Disabled" msgstr "Onemogućen" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:93 src/service/ui/messaging.js:126 msgid "Just now" msgstr "Upravo sada" #. TRANSLATORS: Time duration in minutes (eg. 15 minutes) #: src/service/ui/messaging.js:98 src/service/ui/messaging.js:130 #: src/shell/donotdisturb.js:266 #, fuzzy, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d minuta" msgstr[1] "%d minuta" msgstr[2] "%d minuta" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:103 #, javascript-format msgid "Yesterday・%s" msgstr "Juče・%s" #: src/service/ui/messaging.js:920 msgid "New Message" msgstr "Nova poruka" #. TRANSLATORS: When the battery level is 100% #: src/shell/device.js:86 msgid "Fully Charged" msgstr "Potpuno puna" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:90 #, 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:100 #, 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:108 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (%d∶%02d preostaje)" #: src/shell/donotdisturb.js:150 src/shell/donotdisturb.js:289 msgid "Do Not Disturb" msgstr "Ne uznemiravaj" #: src/shell/donotdisturb.js:156 msgid "Silence Mobile Device Notifications" msgstr "Utišaj obaveštenja mobilinih uređaja" #: src/shell/donotdisturb.js:171 msgid "Until you turn off Do Not Disturb" msgstr "Dok ne isključim Ne uznemiravaj" #. TRANSLATORS: Time until change with time duration #. EXAMPLE: Until 10:00 (2 hours) #: src/shell/donotdisturb.js:184 src/shell/donotdisturb.js:278 #, javascript-format msgid "Until %s (%s)" msgstr "Do %s (%s)" #: src/shell/donotdisturb.js:216 msgid "Done" msgstr "Gotovo" #. TRANSLATORS: Time duration in hours (eg. 2 hours) #: src/shell/donotdisturb.js:263 #, javascript-format msgid "One hour" msgid_plural "%d hours" msgstr[0] "%d čas" msgstr[1] "%d časa" msgstr[2] "%d časova" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:29 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:33 msgid "Service Unavailable" msgstr "Servis nije dostupan" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:37 msgid "Open in Browser" msgstr "Otvori u pregledaču" gnome-shell-extension-gsconnect-20/po/tr.po000066400000000000000000000601251341554142200211120ustar00rootroot00000000000000# Türkçe Çeviri org.gnome.Shell.Extensions.GSConnect. # Copyright © 2018 the org.gnome.Shell.Extensions.GSConnect authors. # This file is distributed under the same license as the org.gnome.Shell.Extensions.GSConnect package. # Serdar Sağlam , Orhan Engin Okay , A. Burak Tektaş , 2018. # msgid "" msgstr "" "Project-Id-Version: org.gnome.Shell.Extensions.GSConnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-12-29 16:44-0800\n" "PO-Revision-Date: 2019-01-08 13:51+0300\n" "Last-Translator: Orhan Engin Okay " "\n" "Language-Team: Türkçe , , " "\n" "Language: tr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/connect.ui:24 data/menus.ui:7 msgid "Connect to…" msgstr "Bağlan…" #. Action Buttons #: data/connect.ui:30 data/notification.ui:14 data/telephony.ui:14 #: src/service/plugins/share.js:139 src/service/plugins/share.js:310 #: src/service/plugins/share.js:456 src/service/ui/device.js:607 #: src/service/ui/keybindings.js:44 src/service/ui/service.js:37 #: src/service/ui/settings.js:539 src/shell/donotdisturb.js:215 msgid "Cancel" msgstr "İptal" #: data/connect.ui:37 msgid "Connect" msgstr "Bağlan" #: data/connect.ui:98 msgid "IP Address" msgstr "IP Adresi" #: data/connect.ui:142 msgid "Bluetooth Device" msgstr "Bluetooth Cihazı" #: data/contacts.ui:49 msgid "Type a phone number or name" msgstr "Telefon numarası veya ad yazın" #: data/contacts.ui:83 msgid "No contacts" msgstr "Kişi yok" #: data/contacts.ui:95 data/menus.ui:33 data/messaging.ui:247 msgid "Help" msgstr "Yardım" #: data/conversation.ui:76 data/conversation.ui:85 src/shell/notification.js:51 msgid "Type a message" msgstr "Mesaj Girin" #: data/conversation.ui:84 msgid "Send Message" msgstr "Mesaj Gönder" #: data/device.ui:67 src/service/plugins/battery.js:12 msgid "Battery" msgstr "Batarya" #: data/device.ui:122 msgid "Clipboard Sync" msgstr "Pano Sekranizasyonu" #: data/device.ui:185 msgid "Media Players" msgstr "Medya Oynatıcılar" #: data/device.ui:240 msgid "Mouse & Keyboard" msgstr "Fare & Klavye" #: data/device.ui:295 msgid "Volume Control" msgstr "Ses Kontrolü" #: data/device.ui:345 data/device.ui:1728 msgid "Sharing" msgstr "Paylaşım" #: data/device.ui:374 data/device.ui:651 data/device.ui:1774 #: src/service/plugins/runcommand.js:18 src/service/plugins/runcommand.js:169 msgid "Commands" msgstr "Komutlar" #: data/device.ui:424 data/device.ui:427 msgid "Name" msgstr "Ad" #: data/device.ui:440 data/device.ui:446 msgid "Command Line" msgstr "Komut Satırı" #: data/device.ui:444 data/device.ui:445 msgid "Choose an executable" msgstr "Yürütülebilir dosya seçin" #: data/device.ui:496 data/device.ui:511 msgid "Add" msgstr "Ekle" #: data/device.ui:527 data/device.ui:542 msgid "Remove" msgstr "Çıkar" #: data/device.ui:576 data/device.ui:588 msgid "Edit" msgstr "Düzenle" #: data/device.ui:604 data/device.ui:616 msgid "Save" msgstr "Kadet" #: data/device.ui:712 msgid "Share Notifications" msgstr "Bildirimleri Paylaş" #: data/device.ui:763 msgid "Applications" msgstr "Uygulamalar" #: data/device.ui:809 data/device.ui:1820 #: src/service/plugins/notification.js:13 msgid "Notifications" msgstr "Bildirimler" #: data/device.ui:839 msgid "Incoming Calls" msgstr "Gelen Çağrılar" #: data/device.ui:888 data/device.ui:1055 msgid "Volume" msgstr "Ses" #: data/device.ui:954 data/device.ui:1120 msgid "Pause Media" msgstr "Medyayı Duraklat" #: data/device.ui:1007 msgid "Ongoing Calls" msgstr "Devam eden Çağrılar" #: data/device.ui:1176 msgid "Mute Microphone" msgstr "Mikrofonu Sustur" #: data/device.ui:1230 data/device.ui:1866 src/service/plugins/telephony.js:13 msgid "Telephony" msgstr "Telefon" #: data/device.ui:1265 msgid "Action Shortcuts" msgstr "Kısayol Eylemleri" #: data/device.ui:1280 data/device.ui:1349 msgid "Reset All…" msgstr "Hepsini Sıfırla…" #: data/device.ui:1334 msgid "Command Shortcuts" msgstr "Komut Kısayolları" #: data/device.ui:1398 msgid "Shortcuts" msgstr "Kısayollar" #: data/device.ui:1429 msgid "Plugins" msgstr "Eklentiler" #: data/device.ui:1475 msgid "Experimental" msgstr "Deneysel" #: data/device.ui:1524 msgid "Legacy SMS Support" msgstr "Eski SMS Desteği" #: data/device.ui:1598 msgid "Delete" msgstr "Sil" #: data/device.ui:1627 msgid "Delete this device" msgstr "Bu cihazı sil" #: data/device.ui:1645 msgid "Unpair and remove all settings and files" msgstr "Tüm ayarları sıfırla ve dosyaları kaldır" #: data/device.ui:1678 data/device.ui:1958 msgid "Advanced" msgstr "Gelişmiş" #: data/device.ui:1912 msgid "Keyboard Shortcuts" msgstr "Klavye Kısayolları" #. TRANSLATORS: Send a pair request to the device #: data/device.ui:2015 data/menus.ui:68 src/service/device.js:424 msgid "Pair" msgstr "Eşleştir" #: data/device.ui:2047 msgid "Device is unpaired" msgstr "Cihaz eşleşmemiş" #: data/device.ui:2062 msgid "You may configure this device before pairing" msgstr "Eşleşmeden önce cihazı yapılandırın" #: data/menus.ui:12 msgid "Display Mode" msgstr "Görünüm Modu" #. TRANSLATORS: Show device indicators in the top bar #: data/menus.ui:15 msgid "Panel" msgstr "" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/menus.ui:21 msgid "User Menu" msgstr "Kullancı Menüsü" #. TRANSLATORS: Generate a support log #: data/menus.ui:29 src/service/ui/settings.js:536 msgid "Generate Support Log" msgstr "Destek Günlüğü Oluştur" #: data/menus.ui:38 msgid "About" msgstr "Hakkında" #. TRANSLATORS: Change the connection type to Bluetooth #: data/menus.ui:49 msgid "Switch to Bluetooth" msgstr "Bluetooth'a Geç" #. TRANSLATORS: Change the connection type to TCP/IP #: data/menus.ui:56 msgid "Switch to LAN" msgstr "LAN'a Geç" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/menus.ui:63 src/service/ui/device.js:317 msgid "Encryption Info" msgstr "Şifreleme Bilgisi" #. TRANSLATORS: Unpair the device and notify it #: data/menus.ui:74 src/service/device.js:432 msgid "Unpair" msgstr "Eşleştirmeyi Bitir" #. TRANSLATORS: Send clipboard content to device #: data/menus.ui:86 msgid "To Device" msgstr "Cihaza" #. TRANSLATORS: Receive clipboard content from the device #: data/menus.ui:92 msgid "From Device" msgstr "Cihazdan" #. TRANSLATORS: Don't change the system volume #: data/menus.ui:104 data/menus.ui:130 msgid "Nothing" msgstr "Hiç Biri" #. TRANSLATORS: Lower the system volume #: data/menus.ui:111 data/menus.ui:137 msgid "Lower" msgstr "Düşük" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the phone ringer #: data/menus.ui:118 data/menus.ui:144 src/service/plugins/telephony.js:194 msgid "Mute" msgstr "Sessiz" #: data/messaging.ui:12 src/service/plugins/sms.js:26 #: src/service/ui/messaging.js:911 msgid "Messaging" msgstr "Mesajlaşma" #: data/messaging.ui:21 src/service/ui/messaging.js:992 msgid "New Conversation" msgstr "Yeni Sohbet" #: data/messaging.ui:110 msgid "No conversation selected" msgstr "Hiçbir sohbet seçilmedi" #: data/messaging.ui:126 msgid "Select or start a conversation" msgstr "Sohbet seç ya da başlat" #: data/messaging.ui:193 data/notification.ui:52 data/telephony.ui:52 msgid "Device is disconnected" msgstr "Cihaz çevrim dışı" #: data/messaging.ui:264 msgid "No Conversations" msgstr "Sohbet yok" #: data/notification.ui:21 data/telephony.ui:21 #: src/service/plugins/share.js:457 msgid "Send" msgstr "Gönder" #: data/settings.ui:10 msgid "Searching for devices…" msgstr "Cihaz aranıyor…" #: data/settings.ui:49 data/settings.ui:63 msgid "Refresh" msgstr "Yenile" #: data/settings.ui:74 data/settings.ui:91 src/extension.js:105 msgid "Mobile Settings" msgstr "Mobil Ayarlar" #: data/settings.ui:144 data/settings.ui:158 msgid "Edit Device Name" msgstr "Cihaz Adını Düzenle" #: data/settings.ui:236 msgid "Devices" msgstr "Cihazlar" #: data/settings.ui:299 msgid "Browser Add-Ons" msgstr "Tarayıcı Eklentileri" #: data/settings.ui:579 msgid "Enable" msgstr "Etkin" #: data/settings.ui:611 msgid "This device is invisible to unpaired devices" msgstr "Bu cihaz eşlekştirilmemiş cihazlara görünmez" #: data/settings.ui:623 src/service/daemon.js:532 msgid "Discovery Disabled" msgstr "Keşif Devre Dışı" #. TRANSLATORS: Share URL by SMS #: data/telephony.ui:9 src/service/daemon.js:718 src/service/plugins/sms.js:50 #: webextension/gettext.js:39 msgid "Send SMS" msgstr "SMS Gönder" #. Service Menu #: src/extension.js:79 src/extension.js:255 msgid "Mobile Devices" msgstr "Mobil Cihazlar" #. TRANSLATORS: %d is the number of devices connected #: src/extension.js:253 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "%d Bağlı" msgstr[1] "%d Bağlı" #. TRANSLATORS: Top-level context menu item for GSConnect #: src/nautilus-gsconnect.py:112 webextension/gettext.js:31 msgid "Send To Mobile Device" msgstr "Mobil Cihaza Gönder" #: src/service/daemon.js:372 msgid "Report" msgstr "Rapor" #: src/service/daemon.js:507 msgid "Authentication Failure" msgstr "Kimlik Doğrulama Hatası" #: src/service/daemon.js:516 msgid "Network Error" msgstr "Ağ Hatası" #: src/service/daemon.js:517 src/service/daemon.js:525 msgid "Click for help troubleshooting" msgstr "Sorun giderme konusunda yardım için tıklayın" #: src/service/daemon.js:524 msgid "PulseAudio Error" msgstr "PulseAudio Hatası" #: src/service/daemon.js:533 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/daemon.js:541 #, javascript-format msgid "%s Plugin Failed To Load" msgstr "%s Eklentisi Yüklenemedi" #: src/service/daemon.js:542 msgid "Click for more information" msgstr "Daha fazla bilgi için tıklayın" #: src/service/daemon.js:724 msgid "Dial Number" msgstr "Numarayı Ara" #: src/service/daemon.js:730 src/service/plugins/share.js:27 msgid "Share File" msgstr "Dosya Paylaşımı" #: src/service/device.js:161 msgid "Not available" msgstr "Müsait Değil" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:165 #, javascript-format msgid "Bluetooth device at %s" msgstr "Bluetooth cihazı %s" #. TRANSLATORS: Label for TLS Certificate fingerprint #. #. Example: #. #. Google Pixel Fingerprint: #. 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 #: src/service/device.js:183 src/service/device.js:185 #, javascript-format msgid "%s Fingerprint:" msgstr "%s Parmak İzi:" #: src/service/device.js:242 msgid "Laptop" msgstr "Dizüstü" #: src/service/device.js:244 msgid "Smartphone" msgstr "Telefon" #: src/service/device.js:246 msgid "Tablet" msgstr "" #: src/service/device.js:248 msgid "Desktop" msgstr "Masaüstü" #: src/service/device.js:408 src/service/ui/device.js:15 msgid "Reconnect" msgstr "Yeniden Bağlan" #: src/service/device.js:416 src/service/ui/device.js:16 #: src/service/ui/settings.js:371 msgid "Settings" msgstr "Ayarlar" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:607 #, javascript-format msgid "Pair Request from %s" msgstr "%s tarafından Gelen Eşleşme İsteği" #: src/service/device.js:614 msgid "Reject" msgstr "Reddet" #: src/service/device.js:619 msgid "Accept" msgstr "Kabul et" #: src/service/plugins/battery.js:188 src/service/plugins/findmyphone.js:19 msgid "Ring" msgstr "Çal" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:197 #, javascript-format msgid "%s: Battery is low" msgstr "%s: Batarya düşük" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:199 #, javascript-format msgid "%d%% remaining" msgstr "%d%% kaldı" #: src/service/plugins/clipboard.js:11 msgid "Clipboard" msgstr "Pano" #: src/service/plugins/clipboard.js:17 msgid "Clipboard Push" msgstr "Panoya Gönder" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Pull" msgstr "Panodan Al" #: src/service/plugins/contacts.js:12 msgid "Contacts" msgstr "Kişiler" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #: src/service/plugins/contacts.js:263 src/service/plugins/telephony.js:173 #: src/service/plugins/telephony.js:224 src/service/plugins/telephony.js:264 #: src/service/ui/contacts.js:156 src/service/ui/contacts.js:397 msgid "Unknown Contact" msgstr "Bilinmeye kişi" #: src/service/plugins/findmyphone.js:13 msgid "Find My Phone" msgstr "Telefonumu Bul" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "Fare altlığı" #: src/service/plugins/mousepad.js:28 src/service/plugins/mousepad.js:569 msgid "Keyboard" msgstr "Klavye" #: src/service/plugins/mousepad.js:223 msgid "Additional Software Required" msgstr "Gerekli Ek Yazılım" #: src/service/plugins/mousepad.js:586 msgid "Keyboard not ready" msgstr "Klavye hazır değil" #: src/service/plugins/mpris.js:10 msgid "MPRIS" msgstr "" #: src/service/plugins/notification.js:26 msgid "Cancel Notification" msgstr "Bildirimi İptal et" #: src/service/plugins/notification.js:34 msgid "Close Notification" msgstr "Bildirimi Kapat" #: src/service/plugins/notification.js:42 msgid "Reply Notification" msgstr "Bildirimi Cevapla" #: src/service/plugins/notification.js:50 msgid "Send Notification" msgstr "Bildirim Gönder" #: src/service/plugins/ping.js:11 src/service/plugins/ping.js:17 #: src/service/plugins/ping.js:46 msgid "Ping" msgstr "" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:53 #, javascript-format msgid "Ping: %s" msgstr "" #: src/service/plugins/runcommand.js:12 msgid "Run Commands" msgstr "Komutları Çalıştır" #: src/service/plugins/sftp.js:12 msgid "SFTP" msgstr "" #: src/service/plugins/sftp.js:18 msgid "Mount" msgstr "" #: src/service/plugins/sftp.js:26 msgid "Unmount" msgstr "Ayır" #: src/service/plugins/sftp.js:159 msgid "All files" msgstr "Tüm Dosyalar" #: src/service/plugins/sftp.js:160 msgid "Camera pictures" msgstr "Kamera resimleri" #: src/service/plugins/sftp.js:417 msgid "Files" msgstr "Dosyalar" #: src/service/plugins/share.js:13 src/service/plugins/share.js:19 msgid "Share" msgstr "Paylaş" #: src/service/plugins/share.js:35 msgid "Share Text" msgstr "Metin Paylaş" #: src/service/plugins/share.js:43 src/service/ui/messaging.js:975 #: src/service/ui/messaging.js:983 msgid "Share Link" msgstr "Bağlantıyı Paylaş" #: src/service/plugins/share.js:132 src/service/plugins/share.js:303 msgid "Starting Transfer" msgstr "Aktarım Başlatılıyor" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:134 #, javascript-format msgid "Receiving “%s” from %s" msgstr "Alınıyor “%s” kaynak %s" #: src/service/plugins/share.js:172 src/service/plugins/share.js:327 msgid "Transfer Successful" msgstr "Aktarım Başarılı" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:174 #, javascript-format msgid "Received “%s” from %s" msgstr "Alınan “%s” kaynak %s" #: src/service/plugins/share.js:180 msgid "Open Folder" msgstr "Klasörü Aç" #: src/service/plugins/share.js:185 msgid "Open File" msgstr "Dosyayı Aç" #: src/service/plugins/share.js:192 src/service/plugins/share.js:335 msgid "Transfer Failed" msgstr "Aktarım Başarısız" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:194 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "Alım başarısız “%s” kaynak %s" #: src/service/plugins/share.js:233 #, 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:305 #, 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:329 #, 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:337 #, 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:408 #, 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:412 msgid "Open when done" msgstr "Tamamlandığında Aç" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:451 #, javascript-format msgid "Send a link to %s" msgstr "Bağlantı gönder %s" #: src/service/plugins/sms.js:13 msgid "SMS" msgstr "" #: src/service/plugins/sms.js:34 msgid "New SMS (URI)" msgstr "Yeni SMS (URI)" #: src/service/plugins/sms.js:42 src/service/plugins/telephony.js:22 msgid "Reply SMS" msgstr "SMS Yanıtla" #: src/service/plugins/sms.js:58 msgid "Share SMS" msgstr "SMS Paylaş" #: src/service/plugins/systemvolume.js:11 msgid "System Volume" msgstr "Sistem Sesi" #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "Çağrıyı sessize al" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:190 msgid "Incoming call" msgstr "Gelen çağrı" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:205 msgid "Ongoing call" msgstr "Devam eden çağrı" #. TRANSLATORS: All other phone number types #: src/service/ui/contacts.js:118 src/service/ui/contacts.js:139 #, javascript-format msgid "%s・Other" msgstr "%s・Diğer" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:123 #, javascript-format msgid "%s・Fax" msgstr "%s・Faks" #. TRANSLATORS: A work phone number #: src/service/ui/contacts.js:127 #, javascript-format msgid "%s・Work" msgstr "%s・İş" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:131 #, javascript-format msgid "%s・Mobile" msgstr "%s・Mobil" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:135 #, javascript-format msgid "%s・Home" msgstr "%s・Ev" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #: src/service/ui/contacts.js:278 src/service/ui/contacts.js:292 #, javascript-format msgid "Send to %s" msgstr "Gönder %s" #: src/service/ui/device.js:608 msgid "Open" msgstr "Aç" #: src/service/ui/device.js:660 src/service/ui/device.js:673 msgid "On" msgstr "Açık" #: src/service/ui/device.js:660 src/service/ui/device.js:673 msgid "Off" msgstr "Kapalı" #: src/service/ui/device.js:798 src/service/ui/device.js:826 #: src/service/ui/device.js:850 msgid "Disabled" msgstr "Devre dışı" #. TRANSLATORS: Title of keyboard shortcut dialog #: src/service/ui/keybindings.js:33 msgid "Set Shortcut" msgstr "Kısayol Ayarla" #. TRANSLATORS: Button to confirm the new shortcut #: src/service/ui/keybindings.js:47 msgid "Set" msgstr "Ayarla" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/service/ui/keybindings.js:54 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "Değiştirmek için yeni bir kısayol girin %s" #. TRANSLATORS: Keys for cancelling (␛) or resetting (␈) a shortcut #: src/service/ui/keybindings.js:83 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ın." #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/service/ui/keybindings.js:182 #, javascript-format msgid "%s is already being used" msgstr "%s zaten kullanılıyor" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:65 src/service/ui/messaging.js:98 msgid "Just now" msgstr "Sadece şimdi" #. TRANSLATORS: Time duration in minutes (eg. 15 minutes) #: src/service/ui/messaging.js:70 src/service/ui/messaging.js:102 #: src/shell/donotdisturb.js:266 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "%d dakika" msgstr[1] "%d dakika" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:75 #, javascript-format msgid "Yesterday・%s" msgstr "Dün %s" #. TRANSLATORS: An outgoing message body in a conversation summary #: src/service/ui/messaging.js:243 #, javascript-format msgid "You: %s" msgstr "Sen: %s" #: src/service/ui/service.js:31 msgid "Select a Device" msgstr "Cihazı Seç" #: src/service/ui/service.js:35 msgid "Select" msgstr "Seç" #: src/service/ui/settings.js:331 msgid "Unpaired" msgstr "Eşleşmemiş" #: src/service/ui/settings.js:333 msgid "Disconnected" msgstr "Başlanmamış" #: src/service/ui/settings.js:336 msgid "Connected" msgstr "Bağlı" #. TRANSLATORS: Description of where directly shared files are stored. #: src/service/ui/settings.js:406 #, javascript-format msgid "Transferred files are placed in the Downloads folder." msgstr "Aktarılan dosyalar İndirilenler klasörüne yerleştirilir" #: src/service/ui/settings.js:499 msgid "A complete KDE Connect implementation for GNOME" msgstr "GNOME için eksiksiz bir KDE Connect uygulaması" #. TRANSLATORS: eg. 'Translator Name ' #: src/service/ui/settings.js:508 msgid "translator-credits" msgstr "Serdar Sağlam , Orhan Engin Okay " ", A. Burak Tektaş " #: src/service/ui/settings.js:537 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," "ardından günlüğü inceleyin." #: src/service/ui/settings.js:540 msgid "Review Log" msgstr "Günlüğü Gözden Geçir" #. TRANSLATORS: When the battery level is 100% #: src/shell/device.js:82 msgid "Fully Charged" msgstr "Şarj Oldu" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:86 #, 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:96 #, 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:104 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "%d%% (Kalan Süre %d∶%02d)" #: src/shell/donotdisturb.js:150 src/shell/donotdisturb.js:289 msgid "Do Not Disturb" msgstr "Rahatsız Etmeyin" #: src/shell/donotdisturb.js:156 msgid "Silence Mobile Device Notifications" msgstr "Mobil Cihaz Bildirimlerini Sessize Alma" #: src/shell/donotdisturb.js:171 msgid "Until you turn off Do Not Disturb" msgstr "Kapatana kadar Rahatsız Etme" #. TRANSLATORS: Time until change with time duration #. EXAMPLE: Until 10:00 (2 hours) #: src/shell/donotdisturb.js:184 src/shell/donotdisturb.js:278 #, javascript-format msgid "Until %s (%s)" msgstr "Kadar %s (%s)" #: src/shell/donotdisturb.js:216 msgid "Done" msgstr "Tamam" #. TRANSLATORS: Time duration in hours (eg. 2 hours) #: src/shell/donotdisturb.js:263 #, javascript-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "%d saat" msgstr[1] "%d saat" #: src/shell/notification.js:42 msgid "Reply" msgstr "Yanıtla" #. TRANSLATORS: Extension name #: webextension/gettext.js:27 msgid "GSConnect" msgstr "" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:29 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şın." #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:33 msgid "Service Unavailable" msgstr "Servis Mevcu değil" #. TRANSLATORS: No devices are known or available #: webextension/gettext.js:35 msgid "No Device Found" msgstr "Cihaz Bulunamadı" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:37 msgid "Open in Browser" msgstr "Tarayıcıda Aç" gnome-shell-extension-gsconnect-20/po/zh_CN.po000066400000000000000000000501621341554142200214660ustar00rootroot00000000000000# Chinese translations for org.gnome.Shell.Extensions.GSConnect package. # Copyright (C) 2018 THE org.gnome.Shell.Extensions.GSConnect'S COPYRIGHT HOLDER # This file is distributed under the same license as the org.gnome.Shell.Extensions.GSConnect package. # Automatically generated, 2018. # msgid "" msgstr "" "Project-Id-Version: org.gnome.Shell.Extensions.GSConnect\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-12-24 09:24-0800\n" "PO-Revision-Date: 2018-12-24 09:24-0800\n" "Last-Translator: Automatically generated\n" "Language-Team: none\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=INTEGER; plural=EXPRESSION;\n" #. TRANSLATORS: Open a dialog to connect to an IP or Bluez device #: data/connect.ui:24 data/menus.ui:7 msgid "Connect to…" msgstr "" #. Action Buttons #: data/connect.ui:30 data/telephony.ui:14 src/service/plugins/share.js:139 #: src/service/plugins/share.js:310 src/service/plugins/share.js:456 #: src/service/ui/device.js:609 src/service/ui/keybindings.js:44 #: src/service/ui/service.js:37 src/service/ui/settings.js:539 #: src/shell/donotdisturb.js:215 msgid "Cancel" msgstr "" #: data/connect.ui:37 msgid "Connect" msgstr "" #: data/connect.ui:98 msgid "IP Address" msgstr "" #: data/connect.ui:142 msgid "Bluetooth Device" msgstr "" #: data/contacts.ui:49 msgid "Type a phone number or name" msgstr "" #: data/contacts.ui:83 msgid "No contacts" msgstr "" #: data/contacts.ui:95 data/menus.ui:33 data/messaging.ui:247 msgid "Help" msgstr "" #: data/conversation.ui:76 data/conversation.ui:85 msgid "Type a message" msgstr "" #: data/conversation.ui:84 msgid "Send Message" msgstr "" #: data/device.ui:67 src/service/plugins/battery.js:12 msgid "Battery" msgstr "" #: data/device.ui:122 msgid "Clipboard Sync" msgstr "" #: data/device.ui:185 msgid "Media Players" msgstr "" #: data/device.ui:240 msgid "Mouse & Keyboard" msgstr "" #: data/device.ui:295 msgid "Volume Control" msgstr "" #: data/device.ui:345 data/device.ui:1625 msgid "Sharing" msgstr "" #: data/device.ui:374 data/device.ui:651 data/device.ui:1671 #: src/service/plugins/runcommand.js:18 src/service/plugins/runcommand.js:169 msgid "Commands" msgstr "" #: data/device.ui:424 data/device.ui:427 msgid "Name" msgstr "" #: data/device.ui:440 data/device.ui:446 msgid "Command Line" msgstr "" #: data/device.ui:444 data/device.ui:445 msgid "Choose an executable" msgstr "" #: data/device.ui:496 data/device.ui:511 msgid "Add" msgstr "" #: data/device.ui:527 data/device.ui:542 msgid "Remove" msgstr "" #: data/device.ui:576 data/device.ui:588 msgid "Edit" msgstr "" #: data/device.ui:604 data/device.ui:616 msgid "Save" msgstr "" #: data/device.ui:712 msgid "Share Notifications" msgstr "" #: data/device.ui:763 msgid "Applications" msgstr "" #: data/device.ui:809 data/device.ui:1717 #: src/service/plugins/notification.js:12 msgid "Notifications" msgstr "" #: data/device.ui:839 msgid "Incoming Calls" msgstr "" #: data/device.ui:888 data/device.ui:1055 msgid "Volume" msgstr "" #: data/device.ui:954 data/device.ui:1120 msgid "Pause Media" msgstr "" #: data/device.ui:1007 msgid "Ongoing Calls" msgstr "" #: data/device.ui:1176 msgid "Mute Microphone" msgstr "" #: data/device.ui:1230 data/device.ui:1763 src/service/plugins/telephony.js:13 msgid "Telephony" msgstr "" #: data/device.ui:1265 msgid "Action Shortcuts" msgstr "" #: data/device.ui:1280 data/device.ui:1349 msgid "Reset All…" msgstr "" #: data/device.ui:1334 msgid "Command Shortcuts" msgstr "" #: data/device.ui:1398 msgid "Shortcuts" msgstr "" #: data/device.ui:1429 msgid "Plugins" msgstr "" #: data/device.ui:1495 msgid "Delete" msgstr "" #: data/device.ui:1524 msgid "Delete this device" msgstr "" #: data/device.ui:1542 msgid "Unpair and remove all settings and files" msgstr "" #: data/device.ui:1575 data/device.ui:1855 msgid "Advanced" msgstr "" #: data/device.ui:1809 msgid "Keyboard Shortcuts" msgstr "" #. TRANSLATORS: Send a pair request to the device #: data/device.ui:1912 data/menus.ui:68 src/service/device.js:429 msgid "Pair" msgstr "" #: data/device.ui:1944 msgid "Device is unpaired" msgstr "" #: data/device.ui:1959 msgid "You may configure this device before pairing" msgstr "" #: data/menus.ui:12 msgid "Display Mode" msgstr "" #. TRANSLATORS: Show device indicators in the top bar #: data/menus.ui:15 msgid "Panel" msgstr "" #. TRANSLATORS: Show devices in the user menu like Bluetooth #: data/menus.ui:21 msgid "User Menu" msgstr "" #. TRANSLATORS: Generate a support log #: data/menus.ui:29 src/service/ui/settings.js:536 msgid "Generate Support Log" msgstr "" #: data/menus.ui:38 msgid "About" msgstr "" #. TRANSLATORS: Change the connection type to Bluetooth #: data/menus.ui:49 msgid "Switch to Bluetooth" msgstr "" #. TRANSLATORS: Change the connection type to TCP/IP #: data/menus.ui:56 msgid "Switch to LAN" msgstr "" #. TRANSLATORS: View the TLS Certificate fingerprint #: data/menus.ui:63 src/service/ui/device.js:322 msgid "Encryption Info" msgstr "" #. TRANSLATORS: Unpair the device and notify it #: data/menus.ui:74 src/service/device.js:437 msgid "Unpair" msgstr "" #. TRANSLATORS: Send clipboard content to device #: data/menus.ui:86 msgid "To Device" msgstr "" #. TRANSLATORS: Receive clipboard content from the device #: data/menus.ui:92 msgid "From Device" msgstr "" #. TRANSLATORS: Don't change the system volume #: data/menus.ui:104 data/menus.ui:130 msgid "Nothing" msgstr "" #. TRANSLATORS: Lower the system volume #: data/menus.ui:111 data/menus.ui:137 msgid "Lower" msgstr "" #. TRANSLATORS: Mute the system volume #. TRANSLATORS: Silence the phone ringer #: data/menus.ui:118 data/menus.ui:144 src/service/plugins/telephony.js:194 msgid "Mute" msgstr "" #: data/messaging.ui:12 src/service/plugins/sms.js:26 #: src/service/ui/messaging.js:902 msgid "Messaging" msgstr "" #: data/messaging.ui:21 src/service/ui/messaging.js:971 msgid "New Conversation" msgstr "" #: data/messaging.ui:110 msgid "No conversation selected" msgstr "" #: data/messaging.ui:126 msgid "Select or start a conversation" msgstr "" #: data/messaging.ui:193 data/telephony.ui:52 msgid "Device is disconnected" msgstr "" #: data/messaging.ui:264 msgid "No Conversations" msgstr "" #: data/settings.ui:10 msgid "Searching for devices…" msgstr "" #: data/settings.ui:49 data/settings.ui:63 msgid "Refresh" msgstr "" #: data/settings.ui:74 data/settings.ui:91 src/extension.js:105 msgid "Mobile Settings" msgstr "" #: data/settings.ui:144 data/settings.ui:158 msgid "Edit Device Name" msgstr "" #: data/settings.ui:236 msgid "Devices" msgstr "" #: data/settings.ui:299 msgid "Browser Add-Ons" msgstr "" #: data/settings.ui:409 msgid "KDE Connect" msgstr "" #: data/settings.ui:579 msgid "Enable" msgstr "" #: data/settings.ui:611 msgid "This device is invisible to unpaired devices" msgstr "" #: data/settings.ui:623 src/service/daemon.js:532 msgid "Discovery Disabled" msgstr "" #. TRANSLATORS: Share URL by SMS #: data/telephony.ui:9 src/service/daemon.js:714 src/service/plugins/sms.js:50 #: webextension/gettext.js:39 msgid "Send SMS" msgstr "" #: data/telephony.ui:21 src/service/plugins/share.js:457 msgid "Send" msgstr "" #. Service Menu #: src/extension.js:79 src/extension.js:255 msgid "Mobile Devices" msgstr "" #. TRANSLATORS: %d is the number of devices connected #: src/extension.js:253 #, javascript-format msgid "%d Connected" msgid_plural "%d Connected" msgstr[0] "" msgstr[1] "" #. TRANSLATORS: Top-level context menu item for GSConnect #: src/nautilus-gsconnect.py:112 webextension/gettext.js:31 msgid "Send To Mobile Device" msgstr "" #: src/service/daemon.js:372 msgid "Report" msgstr "" #: src/service/daemon.js:507 msgid "Authentication Failure" msgstr "" #: src/service/daemon.js:516 msgid "Network Error" msgstr "" #: src/service/daemon.js:517 src/service/daemon.js:525 msgid "Click for help troubleshooting" msgstr "" #: src/service/daemon.js:524 msgid "PulseAudio Error" msgstr "" #: src/service/daemon.js:533 msgid "" "Discovery has been disabled due to the number of devices on this network." msgstr "" #: src/service/daemon.js:541 #, javascript-format msgid "%s Plugin Failed To Load" msgstr "" #: src/service/daemon.js:542 msgid "Click for more information" msgstr "" #: src/service/daemon.js:720 msgid "Dial Number" msgstr "" #: src/service/daemon.js:726 src/service/plugins/share.js:27 msgid "Share File" msgstr "" #: src/service/device.js:166 msgid "Not available" msgstr "" #. TRANSLATORS: Bluetooth address for remote device #: src/service/device.js:170 #, javascript-format msgid "Bluetooth device at %s" msgstr "" #. TRANSLATORS: Label for TLS Certificate fingerprint #. #. Example: #. #. Google Pixel Fingerprint: #. 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 #: src/service/device.js:188 src/service/device.js:190 #, javascript-format msgid "%s Fingerprint:" msgstr "" #: src/service/device.js:247 msgid "Laptop" msgstr "" #: src/service/device.js:249 msgid "Smartphone" msgstr "" #: src/service/device.js:251 msgid "Tablet" msgstr "" #: src/service/device.js:253 msgid "Desktop" msgstr "" #: src/service/device.js:413 src/service/ui/device.js:15 msgid "Reconnect" msgstr "" #: src/service/device.js:421 src/service/ui/device.js:16 #: src/service/ui/settings.js:371 msgid "Settings" msgstr "" #. TRANSLATORS: eg. Pair Request from Google Pixel #: src/service/device.js:612 #, javascript-format msgid "Pair Request from %s" msgstr "" #: src/service/device.js:619 msgid "Reject" msgstr "" #: src/service/device.js:624 msgid "Accept" msgstr "" #: src/service/plugins/battery.js:188 src/service/plugins/findmyphone.js:19 msgid "Ring" msgstr "" #. TRANSLATORS: eg. Google Pixel: Battery is low #: src/service/plugins/battery.js:197 #, javascript-format msgid "%s: Battery is low" msgstr "" #. TRANSLATORS: eg. 15% remaining #: src/service/plugins/battery.js:199 #, javascript-format msgid "%d%% remaining" msgstr "" #: src/service/plugins/clipboard.js:11 msgid "Clipboard" msgstr "" #: src/service/plugins/clipboard.js:17 msgid "Clipboard Push" msgstr "" #: src/service/plugins/clipboard.js:25 msgid "Clipboard Pull" msgstr "" #: src/service/plugins/contacts.js:12 msgid "Contacts" msgstr "" #. Ensure we have a sender #. TRANSLATORS: No name or phone number #: src/service/plugins/contacts.js:263 src/service/plugins/telephony.js:173 #: src/service/plugins/telephony.js:224 src/service/plugins/telephony.js:264 #: src/service/ui/contacts.js:156 src/service/ui/contacts.js:397 msgid "Unknown Contact" msgstr "" #: src/service/plugins/findmyphone.js:13 msgid "Find My Phone" msgstr "" #: src/service/plugins/mousepad.js:14 msgid "Mousepad" msgstr "" #: src/service/plugins/mousepad.js:28 src/service/plugins/mousepad.js:568 msgid "Keyboard" msgstr "" #: src/service/plugins/mousepad.js:223 msgid "Additional Software Required" msgstr "" #: src/service/plugins/mousepad.js:585 msgid "Keyboard not ready" msgstr "" #: src/service/plugins/mpris.js:10 msgid "MPRIS" msgstr "" #: src/service/plugins/notification.js:25 msgid "Cancel Notification" msgstr "" #: src/service/plugins/notification.js:33 msgid "Close Notification" msgstr "" #: src/service/plugins/notification.js:41 msgid "Reply Notification" msgstr "" #: src/service/plugins/notification.js:49 msgid "Send Notification" msgstr "" #: src/service/plugins/ping.js:11 src/service/plugins/ping.js:17 #: src/service/plugins/ping.js:46 msgid "Ping" msgstr "" #. TRANSLATORS: An optional message accompanying a ping, rarely if ever used #. eg. Ping: A message sent with ping #: src/service/plugins/ping.js:53 #, javascript-format msgid "Ping: %s" msgstr "" #: src/service/plugins/runcommand.js:12 msgid "Run Commands" msgstr "" #: src/service/plugins/sftp.js:12 msgid "SFTP" msgstr "" #: src/service/plugins/sftp.js:18 msgid "Mount" msgstr "" #: src/service/plugins/sftp.js:26 msgid "Unmount" msgstr "" #: src/service/plugins/sftp.js:159 msgid "All files" msgstr "" #: src/service/plugins/sftp.js:160 msgid "Camera pictures" msgstr "" #: src/service/plugins/sftp.js:416 msgid "Files" msgstr "" #: src/service/plugins/share.js:13 src/service/plugins/share.js:19 msgid "Share" msgstr "" #: src/service/plugins/share.js:35 msgid "Share Text" msgstr "" #: src/service/plugins/share.js:43 src/service/ui/messaging.js:954 #: src/service/ui/messaging.js:962 msgid "Share Link" msgstr "" #: src/service/plugins/share.js:132 src/service/plugins/share.js:303 msgid "Starting Transfer" msgstr "" #. TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel #: src/service/plugins/share.js:134 #, javascript-format msgid "Receiving “%s” from %s" msgstr "" #: src/service/plugins/share.js:172 src/service/plugins/share.js:327 msgid "Transfer Successful" msgstr "" #. TRANSLATORS: eg. Received 'book.pdf' from Google Pixel #: src/service/plugins/share.js:174 #, javascript-format msgid "Received “%s” from %s" msgstr "" #: src/service/plugins/share.js:180 msgid "Open Folder" msgstr "" #: src/service/plugins/share.js:185 msgid "Open File" msgstr "" #: src/service/plugins/share.js:192 src/service/plugins/share.js:335 msgid "Transfer Failed" msgstr "" #. TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel #: src/service/plugins/share.js:194 #, javascript-format msgid "Failed to receive “%s” from %s" msgstr "" #: src/service/plugins/share.js:233 #, javascript-format msgid "Text Shared By %s" msgstr "" #. TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel #: src/service/plugins/share.js:305 #, javascript-format msgid "Sending “%s” to %s" msgstr "" #. TRANSLATORS: eg. Sent "book.pdf" to Google Pixel #: src/service/plugins/share.js:329 #, javascript-format msgid "Sent “%s” to %s" msgstr "" #. TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel #: src/service/plugins/share.js:337 #, javascript-format msgid "Failed to send “%s” to %s" msgstr "" #. TRANSLATORS: eg. Send files to Google Pixel #: src/service/plugins/share.js:408 #, javascript-format msgid "Send files to %s" msgstr "" #. TRANSLATORS: Mark the file to be opened once completed #: src/service/plugins/share.js:412 msgid "Open when done" msgstr "" #. TRANSLATORS: eg. Send a link to Google Pixel #: src/service/plugins/share.js:451 #, javascript-format msgid "Send a link to %s" msgstr "" #: src/service/plugins/sms.js:13 msgid "SMS" msgstr "" #: src/service/plugins/sms.js:34 msgid "New SMS (URI)" msgstr "" #: src/service/plugins/sms.js:42 src/service/plugins/telephony.js:22 msgid "Reply SMS" msgstr "" #: src/service/plugins/sms.js:58 msgid "Share SMS" msgstr "" #: src/service/plugins/systemvolume.js:11 msgid "System Volume" msgstr "" #: src/service/plugins/telephony.js:30 msgid "Mute Call" msgstr "" #. TRANSLATORS: The phone is ringing #: src/service/plugins/telephony.js:190 msgid "Incoming call" msgstr "" #. TRANSLATORS: A phone call is active #: src/service/plugins/telephony.js:205 msgid "Ongoing call" msgstr "" #. TRANSLATORS: All other phone number types #: src/service/ui/contacts.js:118 src/service/ui/contacts.js:139 #, javascript-format msgid "%s・Other" msgstr "" #. TRANSLATORS: A fax number #: src/service/ui/contacts.js:123 #, javascript-format msgid "%s・Fax" msgstr "" #. TRANSLATORS: A work phone number #: src/service/ui/contacts.js:127 #, javascript-format msgid "%s・Work" msgstr "" #. TRANSLATORS: A mobile or cellular phone number #: src/service/ui/contacts.js:131 #, javascript-format msgid "%s・Mobile" msgstr "" #. TRANSLATORS: A home phone number #: src/service/ui/contacts.js:135 #, javascript-format msgid "%s・Home" msgstr "" #. TRANSLATORS: A phone number (eg. "Send to 555-5555") #: src/service/ui/contacts.js:278 src/service/ui/contacts.js:292 #, javascript-format msgid "Send to %s" msgstr "" #: src/service/ui/device.js:610 msgid "Open" msgstr "" #: src/service/ui/device.js:662 src/service/ui/device.js:675 msgid "On" msgstr "" #: src/service/ui/device.js:662 src/service/ui/device.js:675 msgid "Off" msgstr "" #: src/service/ui/device.js:800 src/service/ui/device.js:838 #: src/service/ui/device.js:862 msgid "Disabled" msgstr "" #. TRANSLATORS: Title of keyboard shortcut dialog #: src/service/ui/keybindings.js:33 msgid "Set Shortcut" msgstr "" #. TRANSLATORS: Button to confirm the new shortcut #: src/service/ui/keybindings.js:47 msgid "Set" msgstr "" #. TRANSLATORS: Summary of a keyboard shortcut function #. Example: Enter a new shortcut to change Messaging #: src/service/ui/keybindings.js:54 #, javascript-format msgid "Enter a new shortcut to change %s" msgstr "" #. TRANSLATORS: Keys for cancelling (␛) or resetting (␈) a shortcut #: src/service/ui/keybindings.js:83 msgid "Press Esc to cancel or Backspace to reset the keyboard shortcut." msgstr "" #. TRANSLATORS: When a keyboard shortcut is unavailable #. Example: [Ctrl]+[S] is already being used #: src/service/ui/keybindings.js:182 #, javascript-format msgid "%s is already being used" msgstr "" #. TRANSLATORS: Less than a minute ago #: src/service/ui/messaging.js:93 src/service/ui/messaging.js:126 msgid "Just now" msgstr "" #. TRANSLATORS: Time duration in minutes (eg. 15 minutes) #: src/service/ui/messaging.js:98 src/service/ui/messaging.js:130 #: src/shell/donotdisturb.js:266 #, javascript-format msgid "%d minute" msgid_plural "%d minutes" msgstr[0] "" msgstr[1] "" #. TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) #: src/service/ui/messaging.js:103 #, javascript-format msgid "Yesterday・%s" msgstr "" #: src/service/ui/service.js:31 msgid "Select a Device" msgstr "" #: src/service/ui/service.js:35 msgid "Select" msgstr "" #: src/service/ui/settings.js:331 msgid "Unpaired" msgstr "" #: src/service/ui/settings.js:333 msgid "Disconnected" msgstr "" #: src/service/ui/settings.js:336 msgid "Connected" msgstr "" #. TODO: Copied from GNOME Control Center; may change #: src/service/ui/settings.js:406 #, javascript-format msgid "" "Transferred files are placed in the Downloads folder." msgstr "" #: src/service/ui/settings.js:499 msgid "A complete KDE Connect implementation for GNOME" msgstr "" #. TRANSLATORS: eg. 'Translator Name ' #: src/service/ui/settings.js:508 msgid "translator-credits" msgstr "" #: src/service/ui/settings.js:537 msgid "" "Debug messages are being logged. Take any steps necessary to reproduce a " "problem then review the log." msgstr "" #: src/service/ui/settings.js:540 msgid "Review Log" msgstr "" #. TRANSLATORS: When the battery level is 100% #: src/shell/device.js:82 msgid "Fully Charged" msgstr "" #. TRANSLATORS: When no time estimate for the battery is available #. EXAMPLE: 42% (Estimating…) #: src/shell/device.js:86 #, javascript-format msgid "%d%% (Estimating…)" msgstr "" #. TRANSLATORS: Estimated time until battery is charged #. EXAMPLE: 42% (1:15 Until Full) #: src/shell/device.js:96 #, 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:104 #, javascript-format msgid "%d%% (%d∶%02d Remaining)" msgstr "" #: src/shell/donotdisturb.js:150 src/shell/donotdisturb.js:289 msgid "Do Not Disturb" msgstr "" #: src/shell/donotdisturb.js:156 msgid "Silence Mobile Device Notifications" msgstr "" #: src/shell/donotdisturb.js:171 msgid "Until you turn off Do Not Disturb" msgstr "" #. TRANSLATORS: Time until change with time duration #. EXAMPLE: Until 10:00 (2 hours) #: src/shell/donotdisturb.js:184 src/shell/donotdisturb.js:278 #, javascript-format msgid "Until %s (%s)" msgstr "" #: src/shell/donotdisturb.js:216 msgid "Done" msgstr "" #. TRANSLATORS: Time duration in hours (eg. 2 hours) #: src/shell/donotdisturb.js:263 #, javascript-format msgid "%d hour" msgid_plural "%d hours" msgstr[0] "" msgstr[1] "" #. TRANSLATORS: Extension name #: webextension/gettext.js:27 msgid "GSConnect" msgstr "" #. TRANSLATORS: Chrome/Firefox WebExtension description #: webextension/gettext.js:29 msgid "Share links with GSConnect, direct to the browser or by SMS." msgstr "" #. TRANSLATORS: WebExtension can't connect to GSConnect #: webextension/gettext.js:33 msgid "Service Unavailable" msgstr "" #. TRANSLATORS: No devices are known or available #: webextension/gettext.js:35 msgid "No Device Found" msgstr "" #. TRANSLATORS: Open URL with the device's browser #: webextension/gettext.js:37 msgid "Open in Browser" msgstr "" gnome-shell-extension-gsconnect-20/src/000077500000000000000000000000001341554142200202725ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/src/_gsconnect.js000066400000000000000000000166041341554142200227610ustar00rootroot00000000000000'use strict'; const Gdk = imports.gi.Gdk; const Gio = imports.gi.Gio; const GIRepository = imports.gi.GIRepository; const GLib = imports.gi.GLib; const Gtk = imports.gi.Gtk; /** * String.format API supporting %s, %d, %x and %f. Used exclusively for gettext. * See: https://github.com/GNOME/gjs/blob/master/modules/format.js */ String.prototype.format = imports.format.format; /** * Application Variables */ gsconnect.app_id = 'org.gnome.Shell.Extensions.GSConnect'; gsconnect.app_path = '/org/gnome/Shell/Extensions/GSConnect'; gsconnect.is_local = gsconnect.extdatadir.startsWith(GLib.get_user_data_dir()); gsconnect.metadata = (() => { let data = GLib.file_get_contents(gsconnect.extdatadir + '/metadata.json')[1]; if (data instanceof Uint8Array) { data = imports.byteArray.toString(data); } return JSON.parse(data); })(); /** * User Directories */ gsconnect.cachedir = GLib.build_filenamev([GLib.get_user_cache_dir(), 'gsconnect']); gsconnect.configdir = GLib.build_filenamev([GLib.get_user_config_dir(), 'gsconnect']); gsconnect.runtimedir = GLib.build_filenamev([GLib.get_user_runtime_dir(), 'gsconnect']); for (let path of [gsconnect.cachedir, gsconnect.configdir, gsconnect.runtimedir]) { GLib.mkdir_with_parents(path, 448); } /** * Setup global object for user or system install */ if (gsconnect.is_local) { // Infer libdir by assuming gnome-shell shares a common prefix with gjs gsconnect.libdir = GIRepository.Repository.get_search_path().find(path => { return path.endsWith('/gjs/girepository-1.0'); }).replace('/gjs/girepository-1.0', ''); // localedir will be a subdirectory of the extension root gsconnect.localedir = GLib.build_filenamev([ gsconnect.extdatadir, 'locale' ]); // schemadir will be a subdirectory of the extension root gsconnect.gschema = Gio.SettingsSchemaSource.new_from_directory( GLib.build_filenamev([gsconnect.extdatadir, 'schemas']), Gio.SettingsSchemaSource.get_default(), false ); } else { let gvc_typelib = GLib.build_filenamev([ gsconnect.metadata.libdir, 'gnome-shell', 'Gvc-1.0.typelib' ]); // Check for the Gvc TypeLib to verify the defined libdir if (GLib.file_test(gvc_typelib, GLib.FileTest.EXISTS)) { gsconnect.libdir = gsconnect.metadata.libdir; // Fallback to assuming a common prefix with GJS } else { let searchPath = GIRepository.Repository.get_search_path(); gsconnect.libdir = searchPath.find(path => { return path.endsWith('/gjs/girepository-1.0'); }).replace('/gjs/girepository-1.0', ''); } // These two should be populated by meson for this system at build time gsconnect.localedir = gsconnect.metadata.localedir; gsconnect.gschema = Gio.SettingsSchemaSource.new_from_directory( gsconnect.metadata.gschemadir, Gio.SettingsSchemaSource.get_default(), false ); } /** * Init Gettext * * If we aren't inside the GNOME Shell process we'll set gettext functions on * the global object, otherwise we'll set them on the global 'gsconnect' object */ imports.gettext.bindtextdomain(gsconnect.app_id, gsconnect.localedir); const Gettext = imports.gettext.domain(gsconnect.app_id); if (typeof _ !== 'function') { window._ = Gettext.gettext; window.ngettext = Gettext.ngettext; window.C_ = Gettext.pgettext; window.N_ = (s) => s; } else { gsconnect._ = Gettext.gettext; gsconnect.ngettext = Gettext.ngettext; gsconnect.C_ = Gettext.pgettext; gsconnect.N_ = (s) => s; } /** * Init GSettings */ gsconnect.settings = new Gio.Settings({ settings_schema: gsconnect.gschema.lookup(gsconnect.app_id, true) }); /** * Register resources */ Gio.Resource.load( GLib.build_filenamev([gsconnect.extdatadir, `${gsconnect.app_id}.gresource`]) )._register(); gsconnect.get_resource = function(rel_path) { let array = Gio.resources_lookup_data( GLib.build_filenamev([gsconnect.app_path, rel_path]), Gio.ResourceLookupFlags.NONE ).toArray(); if (array instanceof Uint8Array) { array = imports.byteArray.toString(array); } else { array = array.toString(); } return array.replace('@EXTDATADIR@', gsconnect.extdatadir); }; /** * DBus Interface Introspection */ gsconnect.dbusinfo = Gio.DBusNodeInfo.new_for_xml( gsconnect.get_resource(`${gsconnect.app_id}.xml`) ); gsconnect.dbusinfo.nodes.forEach(info => info.cache_build()); /** * Install desktop files for user installs */ gsconnect.installService = function() { let confDir = GLib.get_user_config_dir(); let dataDir = GLib.get_user_data_dir(); let homeDir = GLib.get_home_dir(); // DBus Service let dbusDir = GLib.build_filenamev([dataDir, 'dbus-1', 'services']); let dbusFile = `${gsconnect.app_id}.service`; // Desktop Entry let desktopDir = GLib.build_filenamev([dataDir, 'applications']); let desktopFile = `${gsconnect.app_id}.desktop`; // Nautilus Extension let nautDir = GLib.build_filenamev([dataDir, 'nautilus-python/extensions']); let nautScript = GLib.build_filenamev([nautDir, 'nautilus-gsconnect.py']); // WebExtension Manifests let manifestFile = 'org.gnome.shell.extensions.gsconnect.json'; let chrome = gsconnect.get_resource(`${manifestFile}-chrome`); let mozilla = gsconnect.get_resource(`${manifestFile}-mozilla`); let manifests = [ [confDir + '/chromium/NativeMessagingHosts/', chrome], [confDir + '/google-chrome/NativeMessagingHosts/', chrome], [confDir + '/google-chrome-beta/NativeMessagingHosts/', chrome], [confDir + '/google-chrome-unstable/NativeMessagingHosts/', chrome], [homeDir + '/.mozilla/native-messaging-hosts/', mozilla] ]; // If running as a user extension, ensure the DBus service, desktop entry, // Nautilus script and WebExtension manifests are installed. if (gsconnect.is_local) { // DBus Service GLib.mkdir_with_parents(dbusDir, 493); GLib.file_set_contents( GLib.build_filenamev([dbusDir, dbusFile]), gsconnect.get_resource(dbusFile) ); // Desktop Entry GLib.mkdir_with_parents(desktopDir, 493); GLib.file_set_contents( GLib.build_filenamev([desktopDir, desktopFile]), gsconnect.get_resource(desktopFile) ); // Nautilus Extension let script = Gio.File.new_for_path(nautScript); if (!script.query_exists(null)) { GLib.mkdir_with_parents(nautDir, 493); // 0755 in octal script.make_symbolic_link( gsconnect.extdatadir + '/nautilus-gsconnect.py', null ); } // WebExtension Manifests for (let [dir, manifest] of manifests) { GLib.mkdir_with_parents(dir, 493); GLib.file_set_contents( GLib.build_filenamev([dir, manifestFile]), manifest ); } // 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([desktopDir, desktopFile])); GLib.unlink(nautScript); for (let dir of Object.keys(manifests)) { GLib.unlink(GLib.build_filenamev([dir, manifestFile])); } } }; gnome-shell-extension-gsconnect-20/src/extension.js000066400000000000000000000372151341554142200226540ustar00rootroot00000000000000'use strict'; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const Gtk = imports.gi.Gtk; const Main = imports.ui.main; const PanelMenu = imports.ui.panelMenu; const PopupMenu = imports.ui.popupMenu; const AggregateMenu = Main.panel.statusArea.aggregateMenu; // Bootstrap window.gsconnect = { extdatadir: imports.misc.extensionUtils.getCurrentExtension().path }; imports.searchPath.unshift(gsconnect.extdatadir); imports._gsconnect; // Local Imports const _ = gsconnect._; const Device = imports.shell.device; const DoNotDisturb = imports.shell.donotdisturb; const Keybindings = imports.shell.keybindings; const Notification = imports.shell.notification; gsconnect.proxyProperties = function (iface) { let info = gsconnect.dbusinfo.lookup_interface(iface.g_interface_name); for (let property of info.properties) { Object.defineProperty(iface, property.name, { get: () => { try { return iface.get_cached_property(property.name).deep_unpack(); } catch (e) { return null; } }, enumerable: true }); } }; /** * A System Indicator used as the hub for spawning device indicators and * indicating that the extension is active when there are none. */ class ServiceIndicator extends PanelMenu.SystemIndicator { _init() { super._init(); this._activating = false; this._cancellable = new Gio.Cancellable(); this._devices = new Set(); this._menus = {}; this.keybindingManager = new Keybindings.Manager(); // Service Indicator this._indicator = this._addIndicator(); this._indicator.icon_name = 'org.gnome.Shell.Extensions.GSConnect-symbolic'; this._indicator.visible = false; AggregateMenu._indicators.insert_child_at_index(this.indicators, 0); AggregateMenu._gsconnect = this; // Service Menu this._item = new PopupMenu.PopupSubMenuMenuItem(_('Mobile Devices'), true); this._item.icon.icon_name = 'org.gnome.Shell.Extensions.GSConnect-symbolic'; this._item.label.clutter_text.x_expand = true; this.menu.addMenuItem(this._item); AggregateMenu.menu.addMenuItem(this.menu, 4); // Service Menu -> Devices Section this.deviceSection = new PopupMenu.PopupMenuSection(); this.deviceSection.actor.add_style_class_name('gsconnect-device-section'); gsconnect.settings.bind( 'show-indicators', this.deviceSection.actor, 'visible', Gio.SettingsBindFlags.INVERT_BOOLEAN ); this._item.menu.addMenuItem(this.deviceSection); // Service Menu -> Separator this._item.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); // Service Menu -> "Do Not Disturb" this._item.menu.addMenuItem(new DoNotDisturb.MenuItem()); // Service Menu -> "Mobile Settings" this._item.menu.addAction(_('Mobile Settings'), this._settings); // Watch for UI prefs this._gsettingsId = gsconnect.settings.connect( 'changed::show-indicators', this._sync.bind(this) ); // Async setup this._init_async(); } get available() { return Array.from(this.devices).filter(device => { return (device.Connected && device.Paired); }); } get devices() { return this._devices; } async _init_async() { try { // Init the ObjectManager this.manager = await new Promise((resolve, reject) => { Gio.DBusObjectManagerClient.new_for_bus( Gio.BusType.SESSION, Gio.DBusObjectManagerClientFlags.DO_NOT_AUTO_START, 'org.gnome.Shell.Extensions.GSConnect', '/org/gnome/Shell/Extensions/GSConnect', null, this._cancellable, (manager, res) => { try { resolve(Gio.DBusObjectManagerClient.new_for_bus_finish(res)); } catch (e) { reject(e); } } ); }); // Watch for new and removed this._nameOwnerId = this.manager.connect( 'notify::name-owner', this._onNameOwnerChanged.bind(this) ); this._interfaceAddedId = this.manager.connect( 'interface-added', this._onInterfaceAdded.bind(this) ); this._objectRemovedId = this.manager.connect( 'object-removed', this._onObjectRemoved.bind(this) ); this._interfaceProxyPropertiesChangedId = this.manager.connect( 'interface-proxy-properties-changed', this._onInterfacePropertiesChanged.bind(this) ); // If the service is inactive, wait 5s and recheck before activating if (this.manager.name_owner === null) { GLib.timeout_add_seconds(0, 5, () => { if (this.manager.name_owner === null) { this._activate(); } return GLib.SOURCE_REMOVE; }); // Otherwise we need to setup the currently managed devices } else { for (let object of this.manager.get_objects()) { for (let iface of object.get_interfaces()) { this._onInterfaceAdded(this.manager, object, iface); } } } } catch (e) { Gio.DBusError.strip_remote_error(e); if (!e.code || e.code !== Gio.IOErrorEnum.CANCELLED) { logError(e, 'GSConnect'); } } } _onInterfacePropertiesChanged(manager, object, iface, changed, invalidated) { changed = changed.deep_unpack(); if (changed.hasOwnProperty('Connected') || changed.hasOwnProperty('Paired')) { this._sync(); } } _sync() { // Hide status indicator if in Panel mode or no devices are available let panelMode = gsconnect.settings.get_boolean('show-indicators'); this._indicator.visible = (!panelMode && this.available.length); // Show device indicators in Panel mode if available for (let device of this._devices.values()) { let indicator = Main.panel.statusArea[device.g_object_path].actor; indicator.visible = panelMode && this.available.includes(device); let menu = this._menus[device.g_object_path]; menu.actor.visible = !panelMode && this.available.includes(device); menu._title.actor.visible = menu.actor.visible; } // One connected device in User Menu mode if (!panelMode && this.available.length === 1) { let device = this.available[0]; // Hide the menu title and move it to the submenu item this._menus[device.g_object_path]._title.actor.visible = false; this._item.label.text = device.Name; // Destroy any other device's battery if (this._item._battery && this._item._battery.device !== device) { this._item._battery.destroy(); this._item._battery = null; } // Add the battery to the submenu item if (!this._item._battery) { this._item._battery = new Device.Battery({ device: device, opacity: 128 }); this._item.actor.insert_child_below( this._item._battery, this._item._triangleBin ); } } else { if (this.available.length > 1) { //TRANSLATORS: %d is the number of devices connected this._item.label.text = gsconnect.ngettext( '%d Connected', '%d Connected', this.available.length ).format(this.available.length); } else { this._item.label.text = _('Mobile Devices'); } // Destroy any battery in the submenu item if (this._item._battery) { this._item._battery.destroy(); this._item._battery = null; } } } _activate() { if (this._activating) return; this._activating = true; Gio.DBus.session.call( 'org.gnome.Shell.Extensions.GSConnect', '/org/gnome/Shell/Extensions/GSConnect', 'org.freedesktop.Application', 'Activate', new GLib.Variant('(a{sv})', [{}]), null, Gio.DBusCallFlags.NONE, -1, this._cancellable, (connection, res) => { try { this._activating = false; connection.call_finish(res); } catch (e) { // Silence errors } } ); } _settings() { Gio.DBus.session.call( 'org.gnome.Shell.Extensions.GSConnect', '/org/gnome/Shell/Extensions/GSConnect', 'org.freedesktop.Application', 'ActivateAction', new GLib.Variant('(sava{sv})', ['settings', [], {}]), null, Gio.DBusCallFlags.NONE, -1, null, (connection, res) => { try { connection.call_finish(res); } catch (e) { logError(e, 'GSConnect'); } } ); } _onNameOwnerChanged() { try { if (this.manager.name_owner === null) { this._indicator.visible = false; this._activate(); } else { for (let object of this.manager.get_objects()) { for (let iface of object.get_interfaces()) { this._onInterfaceAdded(this.manager, object, iface); } } } } catch (e) { logError(e); } } _onInterfaceAdded(manager, object, iface) { gsconnect.proxyProperties(iface); this.devices.add(iface); // GActions iface.action_group = Gio.DBusActionGroup.get( iface.g_connection, iface.g_name, iface.g_object_path ); // GMenu iface.menu_model = Gio.DBusMenuModel.get( iface.g_connection, iface.g_name, iface.g_object_path ); // GSettings iface.settings = new Gio.Settings({ settings_schema: gsconnect.gschema.lookup( 'org.gnome.Shell.Extensions.GSConnect.Device', true ), path: '/org/gnome/shell/extensions/gsconnect/device/' + iface.Id + '/' }); // Device Indicator let indicator = new Device.Indicator({device: iface}); Main.panel.addToStatusArea(iface.g_object_path, indicator); // Device Menu let menu = new Device.Menu({ device: iface, menu_type: 'list' }); this._menus[iface.g_object_path] = menu; this.deviceSection.addMenuItem(menu); // Keyboard Shortcuts iface._keybindingsChangedId = iface.settings.connect( 'changed::keybindings', this._onKeybindingsChanged.bind(this, iface) ); this._onKeybindingsChanged(iface); // Try activating the device iface.action_group.activate_action('activate', null); this._sync(); } _onObjectRemoved(manager, object) { let iface = object.get_interface('org.gnome.Shell.Extensions.GSConnect.Device'); // Release keybindings iface.settings.disconnect(iface._keybindingsChangedId); iface._keybindings.map(id => this.keybindingManager.remove(id)); // Destroy the indicator Main.panel.statusArea[iface.g_object_path].destroy(); // Destroy the menu this._menus[iface.g_object_path].destroy(); delete this._menus[iface.g_object_path]; this.devices.delete(iface); this._sync(); } async _onKeybindingsChanged(iface) { try { // Reset any existing keybindings if (iface.hasOwnProperty('_keybindings')) { iface._keybindings.map(id => this.keybindingManager.remove(id)); } iface._keybindings = []; // Get the keybindings let keybindings = iface.settings.get_value('keybindings').deep_unpack(); // Apply the keybindings for (let [action, accelerator] of Object.entries(keybindings)) { let [, name, parameter] = Gio.Action.parse_detailed_name(action); let actionId = this.keybindingManager.add( accelerator, () => iface.action_group.activate_action(name, parameter) ); if (actionId !== 0) { iface._keybindings.push(actionId); } } } catch (e) { logError(e); } } // TODO: need hardcoded keybinding for this _openDeviceMenu(indicator) { if (gsconnect.settings.get_boolean('show-indicators')) { indicator.menu.toggle(); } else { Main.panel._toggleMenu(AggregateMenu); this._item.menu.toggle(); this._item.actor.grab_key_focus(); } } destroy() { this._cancellable.cancel(); // Unhook from any ObjectManager events if (this.manager) { this.manager.disconnect(this._interfaceProxyPropertiesChangedId); this.manager.disconnect(this._interfaceAddedId); this.manager.disconnect(this._objectRemovedId); this.manager.disconnect(this._nameOwnerId); // Destroy any remaining devices for (let object of this.manager.get_objects()) { this._onObjectRemoved(this.manager, object); } } // Disconnect any keybindings this.keybindingManager.destroy(); // Disconnect from any GSettings changes gsconnect.settings.disconnect(this._gsettingsId); // Destroy the UI delete AggregateMenu._gsconnect; this.indicators.destroy(); this._item.destroy(); this.menu.destroy(); } } var serviceIndicator = null; function init() { Gtk.IconTheme.get_default().add_resource_path('/org/gnome/Shell/Extensions/GSConnect/icons'); // 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. gsconnect.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(); } function enable() { serviceIndicator = new ServiceIndicator(); Notification.patchGtkNotificationSources(); } function disable() { serviceIndicator.destroy(); serviceIndicator = null; Notification.unpatchGtkNotificationSources(); } gnome-shell-extension-gsconnect-20/src/nautilus-gsconnect.py000066400000000000000000000101701341554142200244700ustar00rootroot00000000000000""" 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 gi gi.require_version('Nautilus', '3.0') gi.require_version('Gio', '2.0') gi.require_version('GLib', '2.0') gi.require_version('GObject', '2.0') from gi.repository import Nautilus, Gio, GLib, GObject import gettext import locale import os.path import subprocess _ = gettext.gettext USER_DIR = os.path.join(GLib.get_user_data_dir(), 'gnome-shell/extensions/gsconnect@andyholmes.github.io') if os.path.exists(USER_DIR): LOCALE_DIR = os.path.join(USER_DIR, 'locale') else: LOCALE_DIR = None class GSConnectShareExtension(GObject.GObject, Nautilus.MenuProvider): """A context menu for sending files via GSConnect.""" def __init__(self): """Initialize Gettext translations""" GObject.Object.__init__(self) try: locale.setlocale(locale.LC_ALL, '') gettext.bindtextdomain('org.gnome.Shell.Extensions.GSConnect', LOCALE_DIR) except: pass self.devices = {} Gio.DBusObjectManagerClient.new_for_bus( Gio.BusType.SESSION, Gio.DBusObjectManagerClientFlags.DO_NOT_AUTO_START, 'org.gnome.Shell.Extensions.GSConnect', '/org/gnome/Shell/Extensions/GSConnect', None, None, None, self._init_async, None) def _init_async(self, source_object, res, user_data): self.manager = source_object.new_for_bus_finish(res) for obj in self.manager.get_objects(): for interface in obj.get_interfaces(): self._on_interface_added(self.manager, obj, interface) self.manager.connect('interface-added', self._on_interface_added) self.manager.connect('object-removed', self._on_object_removed) def _on_interface_added(self, manager, obj, interface): if interface.props.g_interface_name == 'org.gnome.Shell.Extensions.GSConnect.Device': self.devices[interface.props.g_object_path] = ( interface.get_cached_property('Name').unpack(), Gio.DBusActionGroup.get( interface.get_connection(), 'org.gnome.Shell.Extensions.GSConnect', interface.props.g_object_path)) def _on_object_removed(self, manager, obj): del self.devices[obj.props.g_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, window, files): """Return a list of select files to be sent""" # Enumerate capable devices devices = [] for name, actions in self.devices.values(): if actions.get_action_enabled('shareFile'): devices.append([name, actions]) # No capable devices; don't show menu entry if not devices: return # Only accept regular files for uri in files: if uri.get_uri_scheme() != 'file' or uri.is_directory(): return # Context Menu Item menu = Nautilus.MenuItem( name='GSConnectShareExtension::Devices', label=_('Send To Mobile Device'), icon='smartphone-symbolic' ) # Context Menu submenu = Nautilus.Menu() menu.set_submenu(submenu) # Context Submenu Items for name, action_group in devices: item = Nautilus.MenuItem( name='GSConnectShareExtension::Device' + name, label=name, icon='smartphone-symbolic' ) item.connect('activate', self.send_files, files, action_group) submenu.append_item(item) return menu, gnome-shell-extension-gsconnect-20/src/prefs.js000066400000000000000000000017751341554142200217610ustar00rootroot00000000000000'use strict'; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const Gtk = imports.gi.Gtk; // Find the root datadir of the extension function get_datadir() { let m = /@(.+):\d+/.exec((new Error()).stack.split('\n')[1]); return Gio.File.new_for_path(m[1]).get_parent().get_path(); } // Local Imports window.gsconnect = {extdatadir: get_datadir()}; imports.searchPath.unshift(gsconnect.extdatadir); imports._gsconnect; function init() { gsconnect.installService(); Gtk.IconTheme.get_default().add_resource_path(gsconnect.app_path); } function buildPrefsWidget() { let label = new Gtk.Label(); GLib.timeout_add(GLib.PRIORITY_DEFAULT, 0, () => { label.get_toplevel().destroy(); return false; }); let service = Gio.DBusActionGroup.get( Gio.DBus.session, 'org.gnome.Shell.Extensions.GSConnect', '/org/gnome/Shell/Extensions/GSConnect' ); service.list_actions(); service.activate_action('settings', null); return label; } gnome-shell-extension-gsconnect-20/src/service/000077500000000000000000000000001341554142200217325ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/src/service/__init__.js000066400000000000000000000470311341554142200240340ustar00rootroot00000000000000'use strict'; const Gdk = imports.gi.Gdk; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const Gtk = imports.gi.Gtk; /** * Check if we're in a Wayland session (mostly for input synthesis) * https://wiki.gnome.org/Accessibility/Wayland#Bugs.2FIssues_We_Must_Address */ window._WAYLAND = GLib.getenv('XDG_SESSION_TYPE') === 'wayland'; /** * 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 _debugFunc = function(message, prefix = null) { let caller; if (message.stack) { caller = message.stack.split('\n')[0]; message = `${message.message}\n${message.stack}`; } else { message = JSON.stringify(message, null, 2); caller = (new Error()).stack.split('\n')[1]; } // Prepend prefix message = (prefix) ? `${prefix}: ${message}` : message; // Cleanup the stack let [, func, file, line] = caller.match(/([^@]*)@([^:]*):([^:]*)/); let script = file.replace(gsconnect.extdatadir, ''); GLib.log_structured('GSConnect', GLib.LogLevelFlags.LEVEL_MESSAGE, { 'MESSAGE': `[${script}:${func}:${line}]: ${message}`, 'SYSLOG_IDENTIFIER': 'org.gnome.Shell.Extensions.GSConnect', 'CODE_FILE': file, 'CODE_FUNC': func, 'CODE_LINE': line }); }; // Swap the function out for a no-op anonymous function for speed window.debug = gsconnect.settings.get_boolean('debug') ? _debugFunc : () => {}; gsconnect.settings.connect('changed::debug', (settings) => { window.debug = settings.get_boolean('debug') ? _debugFunc : () => {}; }); /** * A simple warning function along the lines of logError() * * @param {Error|string} message - A string or Error to log * @param {string} [prefix] - An optional prefix for the warning */ window.warning = function(message, prefix = null) { message = (message.message) ? message.message : message; message = (prefix) ? `${prefix}: ${message}` : message; GLib.log_structured( 'GSConnect', GLib.LogLevelFlags.LEVEL_WARNING, {MESSAGE: `WARNING: ${message}`} ); }; /** * Convenience function for loading JSON from a file * * @param {Gio.File|string} file - A Gio.File or path to a JSON file * @param {boolean} sync - Default is %false, if %true load synchronously * @return {object} - The parsed object */ JSON.load = function (file, sync = false) { if (typeof file === 'string') { file = Gio.File.new_for_path(file); } if (sync) { let contents = file.load_contents(null)[1]; if (contents instanceof Uint8Array) { contents = imports.byteArray.toString(contents); } return JSON.parse(contents); } else { return new Promise((resolve, reject) => { file.load_contents_async(null, (file, res) => { try { let contents = file.load_contents_finish(res)[1]; if (contents instanceof Uint8Array) { contents = imports.byteArray.toString(contents); } resolve(JSON.parse(contents)); } catch (e) { reject(e); } }); }); } }; /** * Convenience function for dumping JSON to a file * * @param {Gio.File|string} file - A Gio.File or file path * @param {object} obj - The object to write to disk * @param {boolean} sync - Default is %false, if %true load synchronously */ JSON.dump = function (obj, file, sync = false) { if (typeof file === 'string') { file = Gio.File.new_for_path(file); } if (sync) { file.replace_contents( JSON.stringify(obj, null, 2), null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, null ); } else { return new Promise((resolve, reject) => { file.replace_contents_bytes_async( new GLib.Bytes(JSON.stringify(obj, null, 2)), null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, null, (file, res) => { try { file.replace_contents_finish(res); resolve(); } catch (e) { reject(e); } } ); }); } }; /** * 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|ftp)://' + // 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'); /** * Return a string with URLs couched in tags, parseable by Pango and * using the same RegExp as GNOME Shell. * * @param {string} text - The string to be modified * @return {string} - the modified text */ String.prototype.linkify = function(title = null) { let text = GLib.markup_escape_text(this, -1); _urlRegexp.lastIndex = 0; if (title) { return text.replace( _urlRegexp, `$1$2` ); } else { return text.replace(_urlRegexp, '$1$2'); } }; /** * A simple (for now) pre-comparison sanitizer for phone numbers * See: https://github.com/KDE/kdeconnect-kde/blob/master/smsapp/conversationlistmodel.cpp#L200-L210 * * @return {string} - Return the string stripped of leading 0, and ' ()-+' */ String.prototype.toPhoneNumber = function() { return this.replace(/^0*|[ ()+-]/g, ''); }; /** * An implementation of `rm -rf` in Gio */ Gio.File.rm_rf = function(file) { try { if (typeof file === 'string') { file = Gio.File.new_for_path(file); } try { let iter = file.enumerate_children( 'standard::name', Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null ); let info; while ((info = iter.next_file(null))) { Gio.File.rm_rf(iter.get_child(info)); } iter.close(null); } catch (e) { // Silence errors } file.delete(null); } catch (e) { // Silence errors } }; /** * Extend Gio.Menu with some convenience methods for Device menus and working * with menu items. */ Object.defineProperties(Gio.Menu.prototype, { /** * Return the position of an item in the menu by attribute and value * * @param {String} name - The attribute name (eg. 'label', 'action') * @param {*} - The value of the attribute * @return {Number} - The index of the item or %-1 if not found */ '_get': { value: function(name, value) { let len = this.get_n_items(); for (let i = 0; i < len; i++) { try { let item = this.get_item_attribute_value(i, name, null).unpack(); if (item === value) { return i; } } catch (e) { continue; } } return -1; }, enumerable: false }, /** * Remove an item from the menu by attribute and value * * @param {String} name - The attribute name (eg. 'label', 'action') * @param {*} - The value of the attribute * @return {Number} - The index of the removed item or %-1 if not found */ '_remove': { value: function(name, value) { let index = this._get(name, value); if (index > -1) { this.remove(index); } return index; }, enumerable: false }, /** * Add a GMenuItem for a plugin action * * @param {Device.Action} action - The device action to add * @return {Number} - The index of the added item */ 'add_action': { value: function(action, index = -1) { let [label, icon_name] = action.get_state().deep_unpack(); let item = new Gio.MenuItem(); item.set_label(label); 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}`); if (index === -1) { this.append_item(item); return this.get_n_items(); } else { this.insert_item(index, item); return index; } }, enumerable: false }, /** * Remove a GMenuItem by action name, falling back to device.@name if * necessary. * * @param {String} name - Action name of the item to remove * @return {Number} - The index of the removed item or -1 if not found */ 'remove_action': { value: function(name) { let index = this._remove('action', name); if (index === -1) { index = this._remove('action', `device.${name}`); } return index; }, enumerable: false }, /** * Replace the item with the action name @name with @item * * @param {String} name - Action name of the item to remove * @param {Gio.MenuItem} item - The replacement menu item * @return {Number} - The index of the replaced item or -1 if not found */ 'replace_action': { value: function(name, item) { let index = this.remove_action(name); if (index > -1) { this.insert_item(index, item); } return index; }, enumerable: false } }); /** * 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. * * @param {string} cert_path - Absolute path to a x509 certificate in PEM format * @param {string} key_path - Absolute path to a private key in PEM format * * See: https://github.com/KDE/kdeconnect-kde/blob/master/core/kdeconnectconfig.cpp#L119 */ Gio.TlsCertificate.new_for_paths = function (cert_path, key_path) { let cert_exists = GLib.file_test(cert_path, GLib.FileTest.EXISTS); let key_exists = GLib.file_test(key_path, GLib.FileTest.EXISTS); // Create a new certificate and private key if necessary if (!cert_exists || !key_exists) { let proc = new Gio.Subprocess({ argv: [ gsconnect.metadata.bin.openssl, 'req', '-new', '-x509', '-sha256', '-out', cert_path, '-newkey', 'rsa:4096', '-nodes', '-keyout', key_path, '-days', '3650', '-subj', '/O=andyholmes.github.io/OU=GSConnect/CN=' + GLib.uuid_string_random() ], flags: Gio.SubprocessFlags.STDOUT_SILENCE | Gio.SubprocessFlags.STDERR_SILENCE }); proc.init(null); proc.wait_check(null); } return Gio.TlsCertificate.new_from_files(cert_path, key_path); }; Object.defineProperties(Gio.TlsCertificate.prototype, { /** * Compute a SHA1 fingerprint of the certificate. * See: https://gitlab.gnome.org/GNOME/glib/issues/1290 * * @return {string} - A SHA1 fingerprint of the certificate. */ 'fingerprint': { value: function() { if (!this.__fingerprint) { let proc = new Gio.Subprocess({ argv: [gsconnect.metadata.bin.openssl, 'x509', '-noout', '-fingerprint', '-sha1', '-inform', 'pem'], flags: Gio.SubprocessFlags.STDIN_PIPE | Gio.SubprocessFlags.STDOUT_PIPE }); proc.init(null); let stdout = proc.communicate_utf8(this.certificate_pem, null)[1]; this.__fingerprint = /[a-zA-Z0-9:]{59}/.exec(stdout)[0]; proc.wait_check(null); } return this.__fingerprint; }, enumerable: false }, /** * The common name of the certificate. */ 'common_name': { get: function() { if (!this.__common_name) { let proc = new Gio.Subprocess({ argv: [gsconnect.metadata.bin.openssl, 'x509', '-noout', '-subject', '-inform', 'pem'], flags: Gio.SubprocessFlags.STDIN_PIPE | Gio.SubprocessFlags.STDOUT_PIPE }); proc.init(null); let stdout = proc.communicate_utf8(this.certificate_pem, null)[1]; this.__common_name = /[a-zA-Z0-9-]{36}/.exec(stdout)[0]; proc.wait_check(null); } return this.__common_name; }, enumerable: true }, /** * The common name of the certificate. */ 'certificate_der': { get: function() { if (!this.__certificate_der) { let proc = new Gio.Subprocess({ argv: [gsconnect.metadata.bin.openssl, 'x509', '-outform', 'der', '-inform', 'pem'], flags: Gio.SubprocessFlags.STDIN_PIPE | Gio.SubprocessFlags.STDOUT_PIPE }); proc.init(null); let stdout = proc.communicate(new GLib.Bytes(this.certificate_pem), null)[1]; this.__certificate_der = stdout.toArray(); proc.wait_check(null); } return this.__certificate_der; }, enumerable: true } }); /** * Polyfill for GLib.uuid_string_random() (GLib v2.52+) * * Source: https://gist.github.com/jed/982883 */ if (typeof GLib.uuid_string_random !== 'function') { GLib.uuid_string_random = function() { return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (salt) => { return (salt ^ Math.random() * 16 >> salt / 4).toString(16); }); }; } /** * Extend GLib.Variant with a static method to recursively pack a variant * * @param {*} [obj] - May be a GLib.Variant, Array, standard Object or literal. */ function _full_pack(obj) { let packed; let 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 imports.byteArray.ByteArray): 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 (let [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 deep_unpack() a variant * * TODO: this is duplicated in components/dbus.js and it probably shouldn't be, * but dbus.js can stand on it's own if it is... * * @param {*} [obj] - May be a GLib.Variant, Array, standard Object or literal. */ function _full_unpack(obj) { obj = (obj === undefined) ? this : obj; let unpacked; switch (true) { case (obj === null): return obj; case (obj instanceof GLib.Variant): return _full_unpack(obj.deep_unpack()); case (obj instanceof imports.byteArray.ByteArray): return obj; case (typeof obj.map === 'function'): return obj.map(e => _full_unpack(e)); case (typeof obj === 'object'): unpacked = {}; for (let [key, value] of Object.entries(obj)) { // Try to detect and deserialize GIcons try { if (key === 'icon' && value.get_type_string() === '(sv)') { unpacked[key] = Gio.Icon.deserialize(value); } else { unpacked[key] = _full_unpack(value); } } catch (e) { unpacked[key] = _full_unpack(value); } } return unpacked; default: return obj; } } GLib.Variant.prototype.full_unpack = _full_unpack; /** * A convenience functions for connecting/disconnecting Gtk template callbacks */ Gtk.Widget.prototype.connect_template = function() { this.$templateHandlers = []; Gtk.Widget.set_connect_func.call(this, (builder, obj, signalName, handlerName, connectObj, flags) => { this.$templateHandlers.push([ obj, obj.connect(signalName, this[handlerName].bind(this)) ]); }); }; Gtk.Widget.prototype.disconnect_template = function() { Gtk.Widget.set_connect_func.call(this, function() {}); this.$templateHandlers.map(([obj, id]) => obj.disconnect(id)); }; /** * Convenience functions for saving/restoring window geometry */ const _mutter = new Gio.Settings({schema_id: 'org.gnome.mutter'}); Gtk.Window.prototype.restore_geometry = function() { let [width, height] = this.settings.get_value('window-size').deep_unpack(); this.set_default_size(width, height); if (!_mutter.get_boolean('center-new-windows')) { let [x, y] = this.settings.get_value('window-position').deep_unpack(); this.move(x, y); } if (this.settings.get_boolean('window-maximized')) this.maximize(); }; Gtk.Window.prototype.save_geometry = function() { let state = this.get_window().get_state(); let maximized = (state & Gdk.WindowState.MAXIMIZED); this.settings.set_boolean('window-maximized', maximized); if (maximized || (state & Gdk.WindowState.FULLSCREEN)) return; // GLib.Variant.new() can handle arrays just fine let size = this.get_size(); this.settings.set_value('window-size', new GLib.Variant('(ii)', size)); let position = this.get_position(); this.settings.set_value('window-position', new GLib.Variant('(ii)', position)); }; gnome-shell-extension-gsconnect-20/src/service/bluetooth.js000066400000000000000000000452571341554142200243120ustar00rootroot00000000000000'use strict'; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Core = imports.service.core; const DBus = imports.service.components.dbus; /** * org.bluez Interfaces */ const BluezNode = Gio.DBusNodeInfo.new_for_xml(` `); /** * Proxy for org.bluez.Adapter1 interface */ const DEVICE_INFO = BluezNode.lookup_interface('org.bluez.Device1'); const PROFILE_MANAGER_INFO = BluezNode.lookup_interface('org.bluez.ProfileManager1'); const ProfileManager1Proxy = DBus.makeInterfaceProxy(PROFILE_MANAGER_INFO); /** * Service Discovery Protocol Record template and Service UUID */ const SERVICE_UUID = '185f3df4-3268-4e3f-9fca-d4d5059915bd'; const SDP_TEMPLATE = gsconnect.get_resource(`${gsconnect.app_id}.sdp.xml`); function makeSdpRecord(uuid) { return SDP_TEMPLATE.replace( /@UUID@/gi, uuid ).replace( '@UUID_ANDROID@', uuid.replace(/-/gi, '') ); } /** * Bluez Channel Service */ var ChannelService = GObject.registerClass({ GTypeName: 'GSConnectBluetoothChannelService', Implements: [Gio.DBusInterface], Properties: { 'devices': GObject.param_spec_variant( 'devices', 'Devices', 'A list of Bluez devices supporting the KDE Connect protocol', new GLib.VariantType('as'), null, GObject.ParamFlags.READABLE ) } }, class ChannelService extends Gio.DBusProxy { _init() { super._init({ g_bus_type: Gio.BusType.SYSTEM, g_name: 'org.bluez', g_object_path: '/', g_interface_name: 'org.freedesktop.DBus.ObjectManager', g_flags: Gio.DBusProxyFlags.DO_NOT_AUTO_START_AT_CONSTRUCTION }); // Watch the service this._nameOwnerChangedId = this.connect( 'notify::g-name-owner', this._onNameOwnerChanged.bind(this) ); // The full device map this._devices = new Map(); // Asynchronous init this._init_async(); } // A list of device proxies supporting the KDE Connect Service UUID get devices() { let devices = Array.from(this._devices.values()); return devices.filter(device => device.UUIDs.includes(SERVICE_UUID)); } get service() { return Gio.Application.get_default(); } /** * Create a service record and register a profile */ async _register(uuid) { // Export the org.bluez.Profile1 interface for the KDE Connect service this._profile = new DBus.Interface({ g_connection: this.g_connection, g_instance: this, g_interface_info: BluezNode.lookup_interface('org.bluez.Profile1'), g_object_path: gsconnect.app_path + uuid.replace(/-/gi, '') }); // Register our exported profile path let profile = this._profile.get_object_path(); // Set profile options let options = { // Don't require confirmation RequireAuthorization: new GLib.Variant('b', false), // Only allow paired devices RequireAuthentication: new GLib.Variant('b', true), // Service Record (customized to work with Android) ServiceRecord: new GLib.Variant('s', makeSdpRecord(uuid)) }; // Register KDE Connect bluez profile await this._profileManager.RegisterProfile(profile, uuid, options); } async _init_async() { try { await new Promise((resolve, reject) => { this.init_async(GLib.PRIORITY_DEFAULT, null, (obj, res) => { try { obj.init_finish(res); resolve(); } catch (e) { reject(e); } }); }); this._onNameOwnerChanged(); } catch (e) { if (e instanceof Gio.DBusError) { Gio.DBusError.strip_remote_error(e); } warning(`GSConnect: Bluetooth Error: ${e.message}`); this.destroy(); } } vfunc_g_signal(sender_name, signal_name, parameters) { try { // Wait until the name is properly owned if (!this.g_name_owner === null) return; parameters = parameters.deep_unpack(); switch (true) { case (signal_name === 'InterfacesAdded'): this._onInterfacesAdded(...parameters); break; case (signal_name === 'InterfacesRemoved'): this._onInterfacesRemoved(...parameters); break; } } catch (e) { logError(e); } } async _getDeviceProxy(object_path) { try { let proxy = new Gio.DBusProxy({ g_bus_type: Gio.BusType.SYSTEM, g_name: this.g_name_owner, g_object_path: object_path, g_interface_name: 'org.bluez.Device1' }); // Initialize the device proxy await new Promise((resolve, reject) => { proxy.init_async( GLib.PRIORITY_DEFAULT, null, (proxy, res) => { try { resolve(proxy.init_finish(res)); } catch (e) { reject(e); } } ); }); // Properties and Methods DBus.proxyMethods(proxy, DEVICE_INFO); DBus.proxyProperties(proxy, DEVICE_INFO); // Set a null channel proxy._channel = null; return proxy; } catch (e) { warning(e); return undefined; } } /** * org.freedesktop.DBus.ObjectManager.InterfacesAdded * * @param {string} object_path - Path interfaces have been removed from * @param {object[]} - ?? */ async _onInterfacesAdded(object_path, interfaces) { for (let interface_name in interfaces) { // Only handle devices if (interface_name !== 'org.bluez.Device1') continue; // We track all devices in case their service UUIDs change later if (this._devices.has(object_path)) continue; // Setup the device proxy let proxy = await this._getDeviceProxy(object_path); if (proxy === undefined) continue; // Watch for connected/paired changes proxy.__deviceChangedId = proxy.connect( 'g-properties-changed', this._onDeviceChanged.bind(this) ); // Store the proxy and emit notify::devices this._devices.set(proxy.g_object_path, proxy); this.notify('devices'); } } /** * org.freedesktop.DBus.ObjectManager.InterfacesRemoved * * @param {string} object_path - Path interfaces have been removed from * @param {string[]} - List of interface names removed */ async _onInterfacesRemoved(object_path, interfaces) { try { // An empty interface list means the object is being removed if (interfaces.length === 0) return; // Only handle devices if (interfaces[0] !== 'org.bluez.Device1') return; // Get the proxy let proxy = this._devices.get(object_path); if (proxy === undefined) return; // Stop watching for connected/paired changes proxy.disconnect(proxy.__deviceChangedId); this.RequestDisconnection(object_path); // Release the proxy and emit notify::devices this._devices.delete(object_path); this.notify('devices'); } catch (e) { logError(e, object_path); } } _onDeviceChanged(proxy, changed, invalidated) { // Try connecting if the device has just connected or resolved services changed = changed.full_unpack(); if (changed.hasOwnProperty('Connected')) { if (changed.Connected) { this._connectDevice(proxy); } else { this.RequestDisconnection(proxy.g_object_path); } } else if (changed.ServicesResolved) { this._connectDevice(proxy); } } async _onNameOwnerChanged() { try { if (this.g_name_owner === null) { // Ensure we've removed all devices before restarting for (let device of this._devices.values()) { device.disconnect(device.__deviceChangedId); this._devices.delete(device.g_object_path); this.emit('device-removed', device); } // Remove the profile if (this._profile) { this._profile.destroy(); this._profile = null; } if (this._profileManager) { this._profileManager.destroy(); this._profileManager = null; } await this._getManagedObjects(); } else { // Get a profile manager this._profileManager = new ProfileManager1Proxy({ g_bus_type: Gio.BusType.SYSTEM, g_name: 'org.bluez', g_object_path: '/org/bluez' }); await this._profileManager.init_promise(); // Register the service profile await this._register(SERVICE_UUID); let objects = await this._getManagedObjects(); for (let [object_path, object] of Object.entries(objects)) { await this._onInterfacesAdded(object_path, object); } } } catch (e) { logError(e); } } /** * org.freedesktop.DBus.ObjectManager.GetManagedObjects * * @return {object} - Dictionary of managed object paths and interface names */ _getManagedObjects() { return new Promise((resolve, reject) => { this.call( 'GetManagedObjects', null, Gio.DBusCallFlags.NONE, -1, null, (proxy, res) => { try { let variant = proxy.call_finish(res); let objects = variant.deep_unpack()[0]; resolve(objects); } catch (e) { reject(e); } } ); }); } /** * Attempt to connect the service profile to @iface * * @param {Gio.DBusProxy} iface - A org.bluez.Device1 interface proxy */ async _connectDevice(iface) { try { // This device already has a connected or connecting channel if (iface._channel) { debug('already connected', iface.Alias); return; } // Only try connecting paired bluetooth devices if (iface.Paired) { debug('requesting bluetooth connection', iface.Alias); await iface.ConnectProfile(SERVICE_UUID); } } catch (e) { // Silence errors (for now) } } /** * This method gets called when the service daemon unregisters the profile. * A profile can use it to do cleanup tasks. There is no need to unregister * the profile, because when this method gets called it has already been * unregistered. * * @param {undefined} - No parameters * @return {undefined} - void return value */ async Release() { debug('Release'); return; } /** * This method gets called when a new service level connection has been * made and authorized. * * @param {string} - DBus object path * @param {number} - A number for the incoming connection's file-descriptor * @param {object} - An object of properties for the file-descriptor * @return {undefined} - void return value */ async NewConnection(object_path, fd, fd_properties) { debug(`(${object_path}, ${fd}, ${JSON.stringify(fd_properties)})`); let bdevice = this._devices.get(object_path); try { // Create a Gio.SocketConnection from the file-descriptor let socket = Gio.Socket.new_from_fd(fd); let connection = socket.connection_factory_create_connection(); let channel = new Core.Channel(); // TODO: We can't differentiate incoming or outgoing connections so // we treat this as outgoing and write our identity to the socket connection = await channel._sendIdent(connection); // Accept the connection await channel.accept(connection); bdevice._channel = channel; let _id = channel.cancellable.connect(() => { channel.cancellable.disconnect(_id); bdevice._channel = null; }); channel.identity.body.bluetoothHost = bdevice.Address; channel.identity.body.bluetoothPath = bdevice.g_object_path; // Unlike Lan channels, we accept all new connections since they // have to be paired over bluetooth anyways let device = await this.service._ensureDevice(channel.identity); // Attach a device to the channel channel.attach(device); } catch (e) { if (bdevice._channel !== null) { bdevice._channel.close(); bdevice._channel = null; } warning(e, bdevice.Alias); } } /** * This method gets called when a profile gets disconnected. * * The file descriptor is no longer owned by the service daemon and the * profile implementation needs to take care of cleaning up all * connections. * * If multiple file descriptors are indicated via NewConnection, it is * expected that all of them are disconnected before returning from this * method call. * * @param {string} object_path - DBus object path * @return {undefined} - void return value */ async RequestDisconnection(object_path) { debug(object_path); try { let device = this._devices.get(object_path); if (device && device._channel !== null) { debug(`GSConnect: Disconnecting ${device.Alias}`); device._channel.close(); device._channel = null; } } catch (e) { // Silence errors (for now) } } broadcast(object_path) { try { let device = this._devices.get(object_path); if (device) { this._connectDevice(device); } } catch (e) { debug(e, object_path); } } destroy() { for (let device of this._devices.values()) { if (device._channel !== null) { device._channel.close(); device._channel = null; } } if (this._profile) { this._profile.destroy(); } } }); /** * TODO: Bluetooth Base Channel */ var Channel = class Channel extends Core.Channel { get type() { return 'bluetooth'; } }; /** * TODO: Bluetooth Transfer Channel */ var Transfer = class Transfer extends Channel { /** * @param {object} params - Transfer parameters * @param {Device.Device} params.device - The device that owns this transfer * @param {Gio.InputStream} params.input_stream - The input stream (read) * @param {Gio.OutputStream} params.output_stream - The output stream (write) * @param {number} params.size - The size of the transfer in bytes */ constructor(params) { super(params); // The device tracks transfers it owns so they can be closed from the // notification action. this.device._transfers.set(this.uuid, this); } get identity() { return this.device._channel.identity; } /** * Override to untrack the transfer UUID */ close() { this.device._transfers.delete(this.uuid); super.close(); } }; gnome-shell-extension-gsconnect-20/src/service/components/000077500000000000000000000000001341554142200241175ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/src/service/components/contacts.js000066400000000000000000000236151341554142200263020ustar00rootroot00000000000000'use strict'; const GdkPixbuf = imports.gi.GdkPixbuf; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; /** * A store for contacts */ var Store = GObject.registerClass({ GTypeName: 'GSConnectContactsStore', Properties: { 'context': GObject.ParamSpec.string( 'context', 'Context', 'Used as the cache directory, relative to gsconnect.cachedir', GObject.ParamFlags.CONSTRUCT_ONLY | GObject.ParamFlags.READWRITE, '' ) }, 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.__cache_data = {}; // Automatically prepare the desktop store if (context === null) { this.prepare(); } } async __cache_write() { try { if (this.__cache_lock) { this.__cache_queue = true; return; } this.__cache_lock = true; await JSON.dump(this.__cache_data, this.__cache_file); } catch (e) { warning(e); } finally { this.__cache_lock = false; if (this.__cache_queue) { this.__cache_queue = false; this.__cache_write(); } } } *[Symbol.iterator]() { let contacts = this.contacts; for (let i = 0, len = contacts.length; i < len; i++) { yield contacts[i]; } } get contacts() { return Object.values(this.__cache_data); } get context() { return this._context || null; } set context(context) { this._context = context; if (context === null) { // Create a re-usable launcher for folks.py this._launcher = new Gio.SubprocessLauncher({ flags: Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE }); this._launcher.setenv('FOLKS_BACKENDS_DISABLED', 'telepathy', true); this.__cache_dir = Gio.File.new_for_path(gsconnect.cachedir); } else { this.__cache_dir = Gio.File.new_for_path( GLib.build_filenamev([gsconnect.cachedir, context]) ); } GLib.mkdir_with_parents(this.__cache_dir.get_path(), 448); this.__cache_file = this.__cache_dir.get_child('contacts.json'); } /** * Save a ByteArray to file and return the path * * @param {ByteArray} contents - An image ByteArray * @return {string|undefined} - File path or %undefined on failure */ storeAvatar(contents) { return new Promise((resolve, reject) => { let md5 = GLib.compute_checksum_for_data(GLib.ChecksumType.MD5, contents); let file = this.__cache_dir.get_child(`${md5}`); if (file.query_exists(null)) { resolve(file.get_path()); } else { file.replace_contents_bytes_async( new GLib.Bytes(contents), null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, null, (file, res) => { try { file.replace_contents_finish(res); resolve(file.get_path()); } catch (e) { warning(e, 'Storing avatar'); resolve(undefined); } } ); } }); } // TODO: ensure this can only be called once async prepare() { try { this.__cache_data = await JSON.load(this.__cache_file); } catch (e) { debug(e); } finally { this.notify('context'); } } /** * Query the Store for a contact by name and/or number. * * @param {object} query - A query object * @param {string} [query.name] - The contact's name * @param {string} query.number - The contact's number * @return {object} - A contact object */ query(query) { // First look for an existing contact by number let contacts = this.contacts; let matches = []; let qnumber = query.number.toPhoneNumber(); for (let i = 0, len = contacts.length; i < len; i++) { let contact = contacts[i]; for (let num of contact.numbers) { let 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.__cache_data.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) { try { return (this.__cache_data[position]) ? this.__cache_data[position] : null; } catch (e) { 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.__cache_data[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.__cache_data[contact.id]) { this.__cache_data[contact.id] = contact; this.emit('contact-changed', contact.id); } else { this.__cache_data[contact.id] = contact; this.emit('contact-added', contact.id); } // Write if requested if (write) { this.__cache_write(); } } /** * 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.__cache_data[id]) { delete this.__cache_data[id]; this.emit('contact-removed', id); // Write if requested if (write) { this.__cache_write(); } } } async clear() { try { let contacts = this.contacts; for (let i = 0, len = contacts.length; i < len; i++) { await this.remove(contacts[i].id, false); } await this.__cache_write(); } catch (e) { warning(e, 'Clearing contacts'); } } async update(json = {}) { try { let contacts = Object.values(json); for (let i = 0, len = contacts.length; i < len; i++) { let new_contact = contacts[i]; let contact = this.__cache_data[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++) { let contact = contacts[i]; if (!json[contact.id]) { await this.remove(contact.id, false); } } await this.__cache_write(); } catch (e) { warning(e, 'Updating contacts'); } } async _loadFolks() { try { let folks = await new Promise((resolve, reject) => { let proc = this._launcher.spawnv([ gsconnect.extdatadir + '/service/components/folks.py' ]); proc.communicate_utf8_async(null, null, (proc, res) => { try { let [, stdout, stderr] = proc.communicate_utf8_finish(res); if (stderr.length > 0) { throw new Error(stderr); } resolve(JSON.parse(stdout)); } catch (e) { // format python errors e.stack = e.message; e.message = e.stack.split('\n').filter(l => l).pop(); reject(e); } }); }); await this.update(folks); } catch (e) { debug(e, 'Loading folks'); } } }); /** * The service class for this component */ var Service = Store; gnome-shell-extension-gsconnect-20/src/service/components/dbus.js000066400000000000000000000461511341554142200254210ustar00rootroot00000000000000'use strict'; const Gio = imports.gi.Gio; const GjsPrivate = imports.gi.GjsPrivate; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; /** * Some utility methods */ String.prototype.toDBusCase = function(string) { string = string || this; return string.replace(/(?:^\w|[A-Z]|\b\w)/g, (ltr, offset) => { return ltr.toUpperCase(); }).replace(/[\s_-]+/g, ''); }; String.prototype.toCamelCase = function(string) { string = string || this; return string.replace(/(?:^\w|[A-Z]|\b\w)/g, (ltr, offset) => { return (offset === 0) ? ltr.toLowerCase() : ltr.toUpperCase(); }).replace(/[\s_-]+/g, ''); }; String.prototype.toHyphenCase = function(string) { string = string || this; return string.replace(/(?:[A-Z])/g, (ltr, offset) => { return (offset > 0) ? '-' + ltr.toLowerCase() : ltr.toLowerCase(); }).replace(/[\s_]+/g, ''); }; String.prototype.toUnderscoreCase = function(string) { string = string || this; return string.replace(/(?:^\w|[A-Z]|_|\b\w)/g, (ltr, offset) => { if (ltr === '_') return ''; return (offset > 0) ? '_' + ltr.toLowerCase() : ltr.toLowerCase(); }).replace(/[\s-]+/g, ''); }; /** * A convenience function to recursively unpack a GVariant * * @param {*} obj - May be a GLib.Variant, Array, standard Object or literal. * @return {*} - Returns the contents of @obj with any GVariants unpacked to * their native JavaScript equivalents. */ function full_unpack(obj) { let unpacked; switch (true) { case (obj === null): return obj; case (obj instanceof GLib.Variant): return full_unpack(obj.deep_unpack()); case (obj instanceof imports.byteArray.ByteArray): return obj; case (typeof obj.map === 'function'): return obj.map(e => full_unpack(e)); case (typeof obj === 'object'): unpacked = {}; for (let [key, value] of Object.entries(obj)) { // Try to detect and deserialize GIcons try { if (key === 'icon' && value.get_type_string() === '(sv)') { unpacked[key] = Gio.Icon.deserialize(value); } else { unpacked[key] = full_unpack(value); } } catch (e) { unpacked[key] = full_unpack(value); } } return unpacked; default: return obj; } } function _makeOutSignature(args) { var ret = '('; for (var i = 0; i < args.length; i++) ret += args[i].signature; return ret + ')'; } /** * Convert a string of GVariantType to a list of GType * * @param {string} types - A string of GVariantType characters (eg. a{sv}) * @return {Array} - A list of GType constants */ function vtype_to_gtype(types) { if (vtype_to_gtype._cache === undefined) { vtype_to_gtype._cache = {}; } if (!vtype_to_gtype._cache.hasOwnProperty(types)) { let gtypes = []; for (let i = 0; i < types.length; i++) { switch (types[i]) { case 'b': gtypes.push(GObject.TYPE_BOOLEAN); break; case 's': case 'o': case 'g': gtypes.push(GObject.TYPE_STRING); break; case 'h' || 'i': gtypes.push(GObject.TYPE_INT); break; case 'u': gtypes.push(GObject.TYPE_UINT); break; case 'x': gtypes.push(GObject.TYPE_INT64); break; case 't': gtypes.push(GObject.TYPE_UINT64); break; case 'd': gtypes.push(GObject.TYPE_DOUBLE); break; case 'y': gtypes.push(GObject.TYPE_UCHAR); break; // FIXME: assume it's a variant default: gtypes.push(GObject.TYPE_VARIANT); } } vtype_to_gtype._cache[types] = gtypes; } return vtype_to_gtype._cache[types]; } /** * DBus.Interface represents a DBus interface bound to an object instance, meant * to be exported over DBus. */ var Interface = GObject.registerClass({ GTypeName: 'GSConnectDBusInterface' }, class Interface extends GjsPrivate.DBusImplementation { _init(params) { super._init({ g_interface_info: params.g_interface_info }); this._exportee = params.g_instance; if (params.g_object_path) { this.g_object_path = params.g_object_path; } // Bind Object let info = this.get_info(); this._exportMethods(info); this._exportProperties(info); this._exportSignals(info); // 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); } } // HACK: for some reason the getter doesn't work properly on the parent get g_interface_info() { return this.get_info(); } /** * Invoke an instance's method for a DBus method call. Supports promises. */ async _call(info, memberName, parameters, invocation) { // Convert member casing to native casing let nativeName; if (this[memberName]) { nativeName = memberName; } else if (this[memberName.toUnderscoreCase()]) { nativeName = memberName.toUnderscoreCase(); } else if (this[memberName.toCamelCase()]) { nativeName = memberName.toCamelCase(); } let retval; try { parameters = parameters.unpack().map(parameter => { if (parameter.get_type_string() === 'h') { let message = invocation.get_message(); let fds = message.get_unix_fd_list(); let idx = parameter.deep_unpack(); return fds.get(idx); } else { return full_unpack(parameter); } }); // await all method invocations to support Promise returns retval = await this[nativeName].apply(this, parameters); } catch (e) { if (e instanceof GLib.Error) { invocation.return_gerror(e); } else { let name = e.name; if (name.includes('.')) { // likely to be a normal JS error name = `org.gnome.gjs.JSError.${name}`; } invocation.return_dbus_error(name, e.message); logError(e, `${this}: ${memberName}`); } return; } // undefined (no return value) is the empty tuple if (retval === undefined) { retval = new GLib.Variant('()', []); } // Try manually packing a variant try { if (!(retval instanceof GLib.Variant)) { let outArgs = info.lookup_method(memberName).out_args; retval = new GLib.Variant( _makeOutSignature(outArgs), (outArgs.length == 1) ? [retval] : retval ); } invocation.return_value(retval); // Without a response, the client will wait for timeout } catch (e) { invocation.return_dbus_error( 'org.gnome.gjs.JSError.ValueError', 'Service implementation returned an incorrect value type' ); logError(e); } } _exportMethods(info) { if (info.methods.length === 0) { return; } this.connect('handle-method-call', (impl, name, parameters, invocation) => { return this._call.call( this._exportee, this.g_interface_info, name, parameters, invocation ); }); } _get(info, propertyName) { // Look up the property info let propertyInfo = info.lookup_property(propertyName); // Convert to lower_underscore case before getting let value = this[propertyName.toUnderscoreCase()]; // TODO: better pack if (value != undefined) { return new GLib.Variant(propertyInfo.signature, value); } return null; } _set(info, name, value) { value = full_unpack(value); if (!this._propertyCase) { if (this[name.toUnderscoreCase()]) { this._propertyCase = 'toUnderScoreCase'; } else if (this[name.toCamelCase()]) { this._propertyCase = 'toCamelCase'; } } // Convert to lower_underscore case before setting this[name[this._propertyCase]()] = value; } _exportProperties(info) { if (info.properties.length === 0) { return; } this.connect('handle-property-get', (impl, name) => { return this._get.call(this._exportee, info, name); }); this.connect('handle-property-set', (impl, name, value) => { return this._set.call(this._exportee, info, name, value); }); this._exportee.connect('notify', (obj, paramSpec) => { let name = paramSpec.name.toDBusCase(); let propertyInfo = this.g_interface_info.lookup_property(name); if (propertyInfo) { this.emit_property_changed( name, new GLib.Variant( propertyInfo.signature, // Adjust for GJS's '-'/'_' conversion this._exportee[paramSpec.name.replace(/-/gi, '_')] ) ); } }); } _exportSignals(info) { for (let signal of info.signals) { this._exportee.connect(signal.name.toHyphenCase(), (obj, ...args) => { this.emit_signal( signal.name, new GLib.Variant( `(${signal.args.map(arg => arg.signature).join('')})`, args ) ); }); } } destroy() { this.flush(); this.unexport(); GObject.signal_handlers_destroy(this); } }); /** * Wrapper for org.freedesktop.DBus.Properties.Get * * @param {string} name - The property name * @return {*} - A native property value */ function _proxyGetter(name) { let variant; try { if (this.no_cache) { // Call returns '(v)' so unpack the tuple and return that variant variant = this.call_sync( 'org.freedesktop.DBus.Properties.Get', new GLib.Variant('(ss)', [this.g_interface_name, name]), Gio.DBusCallFlags.NONE, -1, null ).deep_unpack()[0]; } } catch (e) { logError(e); } // Fallback to cached property... variant = variant ? variant : this.get_cached_property(name); return variant ? full_unpack(variant) : null; } /** * Wrapper for org.freedesktop.DBus.Properties.Set * * @param {string} name - The property name * @param {string} signature - The property signature * @param {string} value - A native property value */ function _proxySetter(name, signature, value) { // Pack the new value let variant = new GLib.Variant(signature, value); // Set the cached property first this.set_cached_property(name, variant); // Let it run asynchronously and just log any errors this.call( 'org.freedesktop.DBus.Properties.Set', new GLib.Variant('(ssv)', [this.g_interface_name, name, variant]), Gio.DBusCallFlags.NONE, -1, null, (proxy, result) => { try { this.call_finish(result); } catch (e) { logError(e); } } ); } function proxyProperties(iface, info) { let i, properties = info.properties; for (i = 0; i < properties.length; i++) { let property = properties[i]; Object.defineProperty(iface, property.name, { get: _proxyGetter.bind(iface, property.name), set: _proxySetter.bind(iface, property.name, property.signature), enumerable: true }); } } /** * Create proxy wrappers for the methods on an interface */ function _proxyInvoker(method, ...argv) { return new Promise((resolve, reject) => { let signature = method.in_args.map(arg => arg.signature).join(''); let variant = new GLib.Variant(`(${signature})`, argv); this.call(method.name, variant, 0, -1, null, (proxy, res) => { try { res = proxy.call_finish(res); // If return has single arg, only return that or null if (method.out_args.length === 1) { resolve((res) ? res.deep_unpack()[0] : null); // Otherwise return an array (possibly empty) } else { resolve((res) ? res.deep_unpack() : []); } } catch (e) { e.stack = `${method.name}@${this.g_object_path}\n${e.stack}`; reject(e); } }); }); } function proxyMethods(iface, info) { let i, methods = info.methods; for (i = 0; i < methods.length; i++) { let method = methods[i]; iface[method.name] = _proxyInvoker.bind(iface, method); } } /** * A convenience Promise wrapper for Gio.AsyncInitable.init_async(). Unlike the * generic function, this will return the proxy on success instead of a boolean. * * @param {Gio.Cancellable} cancellable - A cancellable or %null * @return {Gio.DBusProxy} - The initted proxy object */ Gio.DBusProxy.prototype.init_promise = function(cancellable = null) { return new Promise((resolve, reject) => { this.init_async(GLib.PRIORITY_DEFAULT, cancellable, (source_object, res) => { try { source_object.init_finish(res); resolve(source_object); } catch (e) { reject(e); } }); }); }; /** * Return a "heavy" Gio.DBusProxy subclass prepped with methods, properties and * signals described by @info. Methods will be wrapped as async functions, * properties as GProperties with notify/bind support and signals as GSignals. * * @param {Gio.DBusInterfaceInfo} info - The supported interface * @return {Gio.DBusProxyClass} - The constructor object for the subclass */ function makeInterfaceProxy(info) { // Cache built proxies (also avoids GType collisions) if (makeInterfaceProxy._cache === undefined) { makeInterfaceProxy._cache = {}; } // Check if we've already prepared a proxy for this interface if (makeInterfaceProxy._cache.hasOwnProperty(info.name)) { return makeInterfaceProxy._cache[info.name]; } // GProperty ParamSpec's let properties_ = { 'no-cache': GObject.ParamSpec.boolean( 'no-cache', 'No Cache', 'Fetch properties synchronously', GObject.ParamFlags.READWRITE, null ) }; for (let i = 0; i < info.properties.length; i++) { let property = info.properties[i]; let flags = 0; if (property.flags & Gio.DBusPropertyInfoFlags.READABLE) { flags |= GObject.ParamFlags.READABLE; } if (property.flags & Gio.DBusPropertyInfoFlags.WRITABLE) { flags |= GObject.ParamFlags.WRITABLE; } switch (true) { case (property.signature === 'b'): properties_[property.name] = GObject.ParamSpec.boolean( property.name, property.name, `${property.name}: automatically populated`, flags, false ); break; case 'sog'.includes(property.signature): properties_[property.name] = GObject.ParamSpec.string( property.name, property.name, `${property.name}: automatically populated`, flags, '' ); break; // TODO: all number types are converted to Number (double) anyways, // but there may be a case where type is relevant on the proxy case 'hiuxtd'.includes(property.signature): properties_[property.name] = GObject.ParamSpec.double( property.name, property.name, `${property.name}: automatically populated`, flags, GLib.MININT32, GLib.MAXINT32, 0.0 ); break; // Fallback to GVariant if it's not a native type default: properties_[property.name] = GObject.param_spec_variant( property.name, property.name, `${property.name}: automatically populated`, new GLib.VariantType(property.signature), null, flags ); } } // GSignal Spec's let signals_ = {}; for (let i = 0; i < info.signals.length; i++) { let signal = info.signals[i]; signals_[signal.name] = { flags: GObject.SignalFlags.RUN_FIRST, param_types: vtype_to_gtype(signal.args.map(arg => arg.signature).join('')) }; } // Register and store the proxy class makeInterfaceProxy._cache[info.name] = GObject.registerClass({ GTypeName: 'PROXY_' + info.name.split('.').join(''), Implements: [Gio.DBusInterface], Properties: properties_, Signals: signals_ }, class InterfaceProxy extends Gio.DBusProxy { _init(params) { super._init(Object.assign({ g_interface_info: info, g_interface_name: info.name, no_cache: false }, params)); // Proxy methods and properties proxyMethods(this, this.g_interface_info); proxyProperties(this, this.g_interface_info); } vfunc_g_properties_changed(changed, invalidated) { for (let name in changed.deep_unpack()) { try { this.notify(name); } catch (e) { logError(e, name); } } } vfunc_g_signal(sender_name, signal_name, parameters) { try { parameters = parameters.deep_unpack(); this.emit(signal_name, ...parameters); } catch (e) { logError(e, signal_name); } } destroy() { GObject.signal_handlers_destroy(this); } }); return makeInterfaceProxy._cache[info.name]; } gnome-shell-extension-gsconnect-20/src/service/components/folks.py000077500000000000000000000306101341554142200256120ustar00rootroot00000000000000#!/usr/bin/env python3 # This has been modified from the work shimming Gee by Hugo Sena Ribeiro. The # original code is available here: https://github.com/hugosenari/folks import hashlib import itertools import json import os.path import re import sys import uuid import gi gi.require_version('Folks', '0.6') from gi.repository import Folks, GLib, GObject import ctypes from ctypes import pythonapi try: ctypes.cdll.LoadLibrary('libgobject-2.0.so') lego = ctypes.CDLL('libgobject-2.0.so') except: ctypes.cdll.LoadLibrary('libgobject-2.0.so.0') lego = ctypes.CDLL('libgobject-2.0.so.0') lego.g_type_name.restype = ctypes.c_char_p lego.g_type_name.argtypes = (ctypes.c_ulonglong,) pythonapi.PyCapsule_GetName.restype = ctypes.c_char_p pythonapi.PyCapsule_GetName.argtypes = (ctypes.py_object,) pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p pythonapi.PyCapsule_GetPointer.argtypes = (ctypes.py_object, ctypes.c_char_p) ############################################################################### # GObject ############################################################################### class _PyGObject_Functions(ctypes.Structure): _fields_ = [ ('pygobject_register_class', ctypes.PYFUNCTYPE(ctypes.c_void_p)), ('pygobject_register_wrapper', ctypes.PYFUNCTYPE(ctypes.c_void_p)), ('pygobject_lookup_class', ctypes.PYFUNCTYPE(ctypes.c_void_p)), ('pygobject_new', ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)), ] def capsule_name(capsule): return pythonapi.PyCapsule_GetName(capsule) def capsule_ptr(capsule): name = capsule_name(capsule) return pythonapi.PyCapsule_GetPointer(capsule, name) class _PyGO_CAPI(object): ''' Static class to that create PyObject (object) from GObject (pointer) ''' _api = None @classmethod def _set_api(cls): addr = capsule_ptr(gi._gobject._PyGObject_API) cls._api = _PyGObject_Functions.from_address(addr) @classmethod def to_object(cls, addr): cls._api or cls._set_api() return cls._api.pygobject_new(addr) ############################################################################### # GType Conversion ############################################################################### INT, ADDRESS, NONE, NOT_IMPLEMENTED = range(4) G_PY_INT = { (GObject.TYPE_BOOLEAN, ctypes.c_bool), (GObject.TYPE_UNICHAR, ctypes.c_ubyte), (GObject.TYPE_UCHAR, ctypes.c_ubyte), (GObject.TYPE_CHAR, ctypes.c_char), (GObject.TYPE_INT, ctypes.c_int), (GObject.TYPE_UINT, ctypes.c_uint), (GObject.TYPE_FLAGS, ctypes.c_uint), } G_PY_ADDRESS = { (GObject.TYPE_LONG, ctypes.c_long), (GObject.TYPE_DOUBLE, ctypes.c_double), (GObject.TYPE_ULONG, ctypes.c_ulong), (GObject.TYPE_INT64, ctypes.c_longlong), (GObject.TYPE_UINT64, ctypes.c_ulonglong), (GObject.TYPE_ENUM, ctypes.c_ulonglong), (GObject.TYPE_FLOAT, ctypes.c_float), (GObject.TYPE_STRING, ctypes.c_char_p), (GObject.TYPE_POINTER, ctypes.c_void_p), (GObject.TYPE_OBJECT, ctypes.c_void_p), (GObject.TYPE_PYOBJECT, ctypes.py_object), } G_PY_NONE = { (GObject.TYPE_NONE, None), (GObject.TYPE_INVALID, None), } G_PY_NOT_IMPLEMENTED = { (GObject.TYPE_PARAM, None), (GObject.TYPE_STRV, None), (GObject.TYPE_VARIANT, None), (GObject.TYPE_BOXED, None), (GObject.TYPE_INTERFACE, None), } TYPES_G_PY = G_PY_INT | G_PY_ADDRESS | G_PY_NONE | G_PY_NOT_IMPLEMENTED TYPES_ID = {hash(gt): (gt, ct, INT) for gt, ct in G_PY_INT} _u = TYPES_ID.update _u({hash(gt): (gt, ct, ADDRESS) for gt, ct in G_PY_ADDRESS}) _u({hash(gt): (gt, ct, NONE) for gt, ct in G_PY_NONE}) _u({hash(gt): (gt, ct, NOT_IMPLEMENTED) for gt, ct in G_PY_NOT_IMPLEMENTED}) def gtype_name_of(gtype_id=0): ''' Return a name of gtype if type is a class this method use glib/gobjec/gtype.c/g_type_name see code https://github.com/GNOME/glib/blob/master/gobject/gtype.c#L3787 ''' name = lego.g_type_name(hash(gtype_id)) return name and name.decode('utf-8') def gtype_and_ctype_of(gtype_id=0): ''' return (GType, ctype) of gtype_id May return (None, None, NOT_IMPLEMENTED) ''' _default = (None, None, NOT_IMPLEMENTED) g_and_c_type = TYPES_ID.get(hash(gtype_id), _default) if not g_and_c_type[0]: name = gtype_name_of(gtype_id) if name: gtype = GObject.GType.from_name(name) parent_id = hash(gtype.parent) parent = TYPES_ID.get(parent_id, _default) g_and_c_type = (gtype, ctypes.c_void_p, parent[2]) return g_and_c_type def from_int(value, gtype_id): py_value = value types = gtype_and_ctype_of(gtype_id) gtype, ctype, ctg = types if gtype and ctype: if gtype.is_a(GObject.TYPE_OBJECT): py_value = _PyGO_CAPI.to_object(value) elif gtype.is_a(GObject.TYPE_GTYPE): py_value = gtype elif gtype.is_a(GObject.TYPE_STRING): py_value = ctype(value).value.decode('utf-8') elif ctg == INT: py_value = ctype(value).value elif ctg == ADDRESS: py_value = ctype.from_address(value) return py_value, gtype, ctype, ctg def c_to_py(value, gtype_id): return from_int(value, gtype_id)[0] ############################################################################### # Gee Iterator Wrappers ############################################################################### class _GeeIterator(object): def __init__(self, obj, it): self.it = it self.obj = obj self.size = None if hasattr(obj, 'get_size'): self.size = obj.get_size() def __iter__(self): it = self.it while it and it.has_next(): it.next() yield it return class GeeListIterator(_GeeIterator): def __init__(self, obj): _GeeIterator.__init__(self, obj, obj.iterator()) self.key_type = GObject.GType.from_name('gint') self.value_type = None if hasattr(obj, 'get_element_type'): self.value_type = obj.get_element_type() def __iter__(self): i = 0 for it in _GeeIterator.__iter__(self): value = it.get() if self.value_type: value = c_to_py(value, self.value_type) yield i, value i += 1 class GeeMapIterator(_GeeIterator): def __init__(self, obj): _GeeIterator.__init__(self, obj, obj.map_iterator()) self.key_type = None self.value_type = None if hasattr(obj, 'get_key_type'): self.key_type = obj.get_key_type() if hasattr(obj, 'get_value_type'): self.value_type = obj.get_value_type() def __iter__(self): for it in _GeeIterator.__iter__(self): key = it.get_key() value = it.get_value() if self.key_type: key = c_to_py(key, self.key_type) if self.value_type: value = c_to_py(value, self.value_type) yield key, value def get_iterator(obj): if hasattr(obj, 'map_iterator'): return GeeMapIterator(obj) if hasattr(obj, 'iterator'): return GeeListIterator(obj) return [] ############################################################################### # Folks ############################################################################### class PhoneFieldDetails(object): def __init__(self, obj): self.field_details = obj self.value_type = obj.get_value_type() self.value = c_to_py(obj.get_value(), self.value_type) params = get_iterator(obj.get_parameters()) self.parameters = {} while (params.it.next()): key = c_to_py(params.it.get_key(), params.key_type) value = c_to_py(params.it.get_value(), params.value_type) self.parameters[key] = value class Individual(object): def __init__(self, individual): self._individual = individual @property def avatar(self): """An avatar for the contact Return a GIcon (GLoadableIcon/GBytesIcon) or None """ return self._individual.get_avatar() @property def display_name(self): """The name of this Individual to display in the UI.""" return self._individual.get_display_name() @property def id(self): """A unique identifier for the Individual.""" return self._individual.get_id() def _get_local_ids(self): for index, local_id in get_iterator(self._individual.get_local_ids()): if local_id: yield local_id @property def local_ids(self): """The IIDs corresponding to Personas in a backend that we fully trust.""" return [lid for lid in self._get_local_ids()] def _get_phone_numbers(self): phone_numbers = self._individual.get_phone_numbers() for key, details in get_iterator(phone_numbers): yield PhoneFieldDetails(details) @property def phone_numbers(self): phone_numbers = [] for phone_number in self._get_phone_numbers(): phone_numbers.append({ 'value': phone_number.value, 'type': phone_number.parameters.get('type', 'unknown') }) return phone_numbers class Aggregator(object): def __init__(self, loop, action): self.loop = loop self.action = action self.cache_dir = os.path.expanduser('~/.cache/gsconnect/_contacts') self.cache_path = os.path.join('contacts.json') self._individuals = {} self._aggregator = Folks.IndividualAggregator.dup() self._aggregator.connect('notify::is-quiescent', self._on_quiescent) self._aggregator.prepare() def _on_quiescent(self, *args): try: self._get_individuals() if not self.action or self.action == 'list': self.dump_contacts() except: pass self.loop.quit() def _get_individuals(self): individuals = self._aggregator.get_individuals() for uid, folk in get_iterator(individuals): self._individuals[uid] = Individual(folk) @property def individuals(self): individuals = self._aggregator.get_individuals() for uid, folk in get_iterator(individuals): yield Individual(folk) def get_contacts(self): contacts = {}; for folk in self._individuals.values(): try: # Skip contacts without phone numbers if not len(folk.phone_numbers): continue folk_id = folk.id or str(uuid.uuid4()) # Add the contact contacts[folk_id] = { 'id': folk_id, 'name': folk.display_name, 'numbers': folk.phone_numbers, 'origin': 'folks' } # Avatar if folk.avatar != None: if hasattr(folk.avatar, 'get_file'): contacts[folk_id]['avatar'] = folk.avatar.get_file().get_path() elif hasattr(avatar, 'get_bytes'): path = os.path.join(self.cache_dir, folk_id + '.jpeg') with open(path, 'wb') as fobj: fobj.write(folk.avatar.get_bytes().get_data()) contacts[folk_id]['avatar'] = path # Phony timestamp cbytes = json.dumps(contacts[folk_id]).encode('utf-8') contacts[folk_id]['timestamp'] = hashlib.md5(cbytes).hexdigest() except: pass return contacts def dump_contacts(self): contacts = self.get_contacts() print(json.dumps(contacts)) def write(self, new_cache): # if new_cache is empty goa might not be running, avoid wiping contacts if not new_cache: return with open(self.cache_path, 'w') as cache_file: json.dump(new_cache, cache_file) ############################################################################### # main ############################################################################### if __name__ == '__main__': loop = GLib.MainLoop() # Default to dumping contacts action = 'list' if len(sys.argv) == 1 else sys.argv[1] Aggregator(loop, action) loop.run() gnome-shell-extension-gsconnect-20/src/service/components/mpris.js000066400000000000000000000156751341554142200256250ustar00rootroot00000000000000'use strict'; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const DBus = imports.service.components.dbus; /** * org.mpris.MediaPlayer2 Proxy * https://specifications.freedesktop.org/mpris-spec/latest/Media_Player.html */ const MediaPlayer2Proxy = DBus.makeInterfaceProxy( gsconnect.dbusinfo.lookup_interface('org.mpris.MediaPlayer2') ); /** * org.mpris.MediaPlayer2.Player Proxy * https://specifications.freedesktop.org/mpris-spec/latest/Player_Interface.html */ const PlayerProxy = DBus.makeInterfaceProxy( gsconnect.dbusinfo.lookup_interface('org.mpris.MediaPlayer2.Player') ); var Manager = GObject.registerClass({ GTypeName: 'GSConnectMPRISManager', Implements: [Gio.DBusInterface], Properties: { 'identities': GObject.param_spec_variant( 'identities', 'IdentityList', 'A list of MediaPlayer2.Identity for each player', new GLib.VariantType('as'), null, GObject.ParamFlags.READABLE ), // Actually returns an Object of MediaPlayer2Proxy objects, // Player.Identity as key 'players': GObject.param_spec_variant( 'players', 'PlayerList', 'A list of known devices', new GLib.VariantType('a{sv}'), null, GObject.ParamFlags.READABLE ) }, Signals: { 'player-changed': { flags: GObject.SignalFlags.RUN_FIRST, param_types: [GObject.TYPE_OBJECT] }, 'player-seeked': { flags: GObject.SignalFlags.RUN_FIRST, param_types: [GObject.TYPE_OBJECT] } } }, class Manager extends Gio.DBusProxy { _init() { super._init({ g_bus_type: Gio.BusType.SESSION, g_name: 'org.freedesktop.DBus', g_object_path: '/org/freedesktop/DBus' }); // Asynchronous setup this._init_async(); } async _init_async() { try { await this.init_promise(); // Add the current players let names = await this._listNames(); for (let i = 0, len = names.length; i < len; i++) { let name = names[i]; if (name.startsWith('org.mpris.MediaPlayer2')) { this._addPlayer(name); } } } catch (e) { // FIXME: if something goes wrong the component will appear active logError(e); this.destroy(); } } get identities () { return Array.from(this.players.keys()); } get players () { if (this._players === undefined) { this._players = new Map(); } return this._players; } get paused() { if (this._paused === undefined) { this._paused = new Map(); } return this._paused; } get service() { return Gio.Application.get_default(); } vfunc_g_signal(sender_name, signal_name, parameters) { try { if (signal_name === 'NameOwnerChanged') { let [name, old_owner, new_owner] = parameters.deep_unpack(); if (name.startsWith('org.mpris.MediaPlayer2')) { if (new_owner.length) { this._addPlayer(name); } else if (old_owner.length) { this._removePlayer(name); } } } } catch (e) { debug(e); } } _listNames() { return new Promise((resolve, reject) => { this.call( 'org.freedesktop.DBus.ListNames', null, Gio.DBusCallFlags.NONE, -1, null, (proxy, res) => { try { res = proxy.call_finish(res); resolve(res.deep_unpack()[0]); } catch (e) { reject(e); } } ); }); } async _addPlayer(name) { try { let mediaPlayer = await new MediaPlayer2Proxy({ g_bus_type: Gio.BusType.SESSION, g_name: name, g_object_path: '/org/mpris/MediaPlayer2' }).init_promise(); if (!this.players.has(mediaPlayer.Identity)) { debug(`Adding MPRIS Player ${mediaPlayer.Identity}`); let player = await new PlayerProxy({ g_bus_type: Gio.BusType.SESSION, g_name: name, g_object_path: '/org/mpris/MediaPlayer2', no_cache: true }).init_promise(); player.Identity = mediaPlayer.Identity.slice(0); player._propertiesId = player.connect( 'g-properties-changed', (player) => this.emit('player-changed', player) ); player._seekedId = player.connect( 'Seeked', (player) => this.emit('player-seeked', player) ); this.players.set(player.Identity, player); this.notify('players'); } } catch (e) { debug(e); } } async _removePlayer(name) { try { for (let [identity, player] of this.players.entries()) { if (player.g_name === name) { debug(`Removing MPRIS Player ${identity}`); player.disconnect(player._propertiesId); player.disconnect(player._seekedId); player.destroy(); this.paused.delete(identity); this.players.delete(identity); this.notify('players'); } } } catch (e) { debug(e); } } /** * A convenience function for pausing all players currently playing. */ pauseAll() { for (let [identity, player] of this.players.entries()) { if (player.PlaybackStatus === 'Playing' && player.CanPause) { player.Pause(); this.paused.set(identity, player); } } } /** * A convenience function for restarting all players paused with pauseAll(). */ unpauseAll() { for (let player of this.paused.values()) { if (player.PlaybackStatus === 'Paused' && player.CanPlay) { player.Play(); } } this.paused.clear(); } destroy() { for (let player of this.players.values()) { player.disconnect(player._propertiesId); player.disconnect(player._seekedId); player.destroy(); } GObject.signal_handlers_destroy(this); } }); /** * The service class for this component */ var Service = Manager; gnome-shell-extension-gsconnect-20/src/service/components/notification.js000066400000000000000000000340771341554142200271560ustar00rootroot00000000000000'use strict'; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GjsPrivate = imports.gi.GjsPrivate; let _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. */ var Listener = class Listener { constructor() { // 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) ); this._onSettingsChanged(); // Cache for appName->desktop-id lookups this._names = {}; // Asynchronous setup this._init_async(); } get application() { return Gio.Application.get_default(); } get applications() { if (this._applications === undefined) { this._applications = {}; } return this._applications; } /** * Update application notification settings */ _onSettingsChanged() { this._applications = {}; for (let app of this._settings.get_strv('application-children')) { let appSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.notifications.application', path: `/org/gnome/desktop/notifications/application/${app}/` }); let appInfo = Gio.DesktopAppInfo.new( appSettings.get_string('application-id') ); if (appInfo !== null) { this._applications[appInfo.get_name()] = appSettings; } } } /** * Setup a dedicated DBus connection for monitoring */ _newConnection() { return new Promise((resolve, reject) => { Gio.DBusConnection.new_for_address( Gio.dbus_address_get_for_bus_sync(Gio.BusType.SESSION, null), Gio.DBusConnectionFlags.AUTHENTICATION_CLIENT, null, null, (connection, res) => { try { resolve(Gio.DBusConnection.new_for_address_finish(res)); } catch (e) { reject(e); } } ); }); } _getConnection(type = Gio.BusType.SESSION) { return new Promise((resolve, reject) => { Gio.bus_get(type, null, (connection, res) => { try { resolve(Gio.bus_get_finish(res)); } catch (e) { reject(e); } }); }); } /** * Introduce the monitoring connection to DBus */ _helloConnection() { return new Promise((resolve, reject) => { this._monitor.call( 'org.freedesktop.DBus', '/org/freedesktop/DBus', 'org.freedesktop.DBus', 'Hello', null, null, Gio.DBusCallFlags.NONE, -1, null, (connection, res) => { try { resolve(connection.call_finish(res)); } catch (e) { reject(e); } } ); }); } _listNames() { return new Promise((resolve, reject) => { this._session.call( 'org.freedesktop.DBus', '/org/freedesktop/DBus', 'org.freedesktop.DBus', 'ListNames', null, null, Gio.DBusCallFlags.NONE, -1, null, (connection, res) => { try { res = connection.call_finish(res); resolve(res.deep_unpack()[0]); } catch (e) { reject(e); } } ); }); } _getNameOwner(name) { return new Promise((resolve, reject) => { this._session.call( 'org.freedesktop.DBus', '/org/freedesktop/DBus', 'org.freedesktop.DBus', 'GetNameOwner', new GLib.Variant('(s)', [name]), null, Gio.DBusCallFlags.NONE, -1, null, (connection, res) => { try { res = connection.call_finish(res); resolve(res.deep_unpack()[0]); } catch (e) { reject(e); } } ); }); } /** * Try and find a well-known name for @sender on the session bus * * @param {string} sender - A DBus unique name (eg. :1.2282) * @param {string} appName - @appName passed to Notify() (Optional) * @return {string} - A well-known name or %null */ async _getAppId(sender, appName) { try { // Get a list of well-known names, ignoring @sender let names = await this._listNames(); names.splice(names.indexOf(sender), 1); // Make a short list for substring matches (fractal/org.gnome.Fractal) let appLower = appName.toLowerCase(); let shortList = names.filter(name => { return name.toLowerCase().includes(appLower); }); // Run the short list first for (let name of shortList) { let nameOwner = await this._getNameOwner(name); if (nameOwner === sender) { return name; } names.splice(names.indexOf(name), 1); } // Run the full list for (let name of names) { let 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 - (Optional) appName supplied by Notify() * @return {string} - A well-known name or %null */ async _getAppName(sender, appName) { // Check the cache first if (appName && this._names.hasOwnProperty(appName)) { return this._names[appName]; } let appId, appInfo; try { appId = await this._getAppId(sender, appName); appInfo = Gio.DesktopAppInfo.new(`${appId}.desktop`); this._names[appName] = appInfo.get_name(); appName = appInfo.get_name(); } catch (e) { // Silence errors } return appName; } /** * Callback for AddNotification()/Notify() */ async _onHandleMethodCall(impl, 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') { // Try to brute-force an application name using DBus if (!this.applications.hasOwnProperty(parameters[0])) { let sender = invocation.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 */ _monitorConnection() { return new Promise((resolve, reject) => { // 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' ); // 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 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, (connection, res) => { try { resolve(connection.call_finish(res)); } catch (e) { reject(e); } } ); }); } async _init_async() { try { this._session = await this._getConnection(); this._monitor = await this._newConnection(); await this._helloConnection(); await this._monitorConnection(); } catch (e) { // FIXME: if something goes wrong the component will appear active logError(e); this.destroy(); } } _sendNotification(notif) { // Check if this application is disabled in desktop settings let appSettings = this.applications[notif.appName]; if (appSettings && !appSettings.get_boolean('enable')) { return; } // Send the notification to each supporting device let variant = GLib.Variant.full_pack(notif); for (let device of this.application._devices.values()) { device.activate_action('sendNotification', variant); } } Notify(appName, replacesId, iconName, summary, body, actions, hints, timeout) { try { // 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 }); } catch (e) { debug(e); } } AddNotification(application, id, notification) { try { // Ignore our own GNotifications if (application === 'org.gnome.Shell.Extensions.GSConnect') { return; } let 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 }); } catch (e) { debug(e); } } destroy() { try { this._fdoNotifications.disconnect(this._fdoMethodCallId); this._fdoNotifications.flush(); this._fdoNotifications.unexport(); this._gtkNotifications.disconnect(this._gtkMethodCallId); this._gtkNotifications.flush(); this._gtkNotifications.unexport(); this._settings.disconnect(this._settingsId); // TODO: Gio.IOErrorEnum: The connection is closed //this._monitor.close_sync(null); } catch (e) { debug(e); } } }; /** * The service class for this component */ var Service = Listener; gnome-shell-extension-gsconnect-20/src/service/components/pulseaudio.js000066400000000000000000000127251341554142200266360ustar00rootroot00000000000000'use strict'; const Tweener = imports.tweener.tweener; const Gio = imports.gi.Gio; const GIRepository = imports.gi.GIRepository; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; // Add gnome-shell's typelib dir to the search path let typelibDir = GLib.build_filenamev([gsconnect.libdir, 'gnome-shell']); GIRepository.Repository.prepend_search_path(typelibDir); GIRepository.Repository.prepend_library_path(typelibDir); const Gvc = imports.gi.Gvc; /** * Extend Gvc.MixerStream with a property for returning a user-visible name */ Object.defineProperty(Gvc.MixerStream.prototype, 'display_name', { get: function() { try { if (!this.get_ports().length) return this.description; return `${this.get_port().human_port} (${this.description})`; } catch (e) { return this.description; } } }); /** * A convenience wrapper for Gvc.MixerStream */ class Stream { constructor(mixer, stream) { this._max = mixer.get_vol_max_norm(); this._stream = stream; } 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 */ fade(value) { Tweener.removeTweens(this); if (this._stream.volume > value) { Tweener.addTween(this, { volume: value, time: 1, transition: 'easeOutCubic' }); } else if (this._stream.volume < value) { Tweener.addTween(this, { volume: value, time: 1, transition: 'easeInCubic' }); } } } /** * 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. */ var Mixer = GObject.registerClass({ GTypeName: 'GSConnectAudioMixer' }, class Mixer extends Gvc.MixerControl { _init(params) { super._init({name: 'GSConnect'}); this.open(); this._previousVolume = undefined; this._volumeMuted = false; this._microphoneMuted = false; } 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 { let sink = this.get_default_sink(); this._output = (sink) ? new Stream(this, sink) : null; } catch (e) { logError(e); } } vfunc_default_source_changed(id) { try { let 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 */ lowerVolume() { try { if (this.output.volume > 0.15) { this._previousVolume = Number(this.output.volume); this.output.fade(0.15); } } catch (e) { logError(e); } } /** * Mute the output volume (speakers) */ muteVolume() { try { if (!this.output.muted) { this.output.muted = true; this._volumeMuted = true; } } catch (e) { logError(e); } } /** * Mute the input volume (microphone) */ muteMicrophone() { try { if (!this.input.muted) { 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); } } }); /** * The service class for this component */ var Service = Mixer; gnome-shell-extension-gsconnect-20/src/service/components/upower.js000066400000000000000000000022751341554142200260040ustar00rootroot00000000000000'use strict'; const GObject = imports.gi.GObject; const UPower = imports.gi.UPowerGlib; var Battery = GObject.registerClass({ GTypeName: 'GSConnectSystemBattery', Signals: { 'changed': {flags: GObject.SignalFlags.RUN_FIRST} } }, class Battery extends UPower.Device { _init() { super._init(); // This will throw an exception this.set_object_path_sync( '/org/freedesktop/UPower/devices/DisplayDevice', null ); } vfunc_notify(pspec) { try { switch (pspec.get_name()) { case 'percentage': case 'state': case 'warning-level': this.emit('changed'); } } catch (e) { } } get charging() { return (this.state !== UPower.DeviceState.DISCHARGING); } get level() { return this.percentage; } // TODO: reset on charging get threshold() { if (!this.charging && this.warning_level >= UPower.DeviceLevel.LOW) { return 1; } else { return 0; } } }); /** * The service class for this component */ var Service = Battery; gnome-shell-extension-gsconnect-20/src/service/core.js000066400000000000000000000316421341554142200232260ustar00rootroot00000000000000'use strict'; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; /** * Packet * * The packet class is a simple Object-derived class. It only exists to offer * conveniences for coercing to a string writable to a channel and constructing * from Strings and Objects. In future, it could probably be optimized to avoid * excessive shape-trees since it's the most common object in the protocol. */ var Packet = class Packet { constructor(data = null) { this.id = 0; this.type = undefined; this.body = {}; if (data === null) { return; } else if (typeof data === 'string') { this.fromString(data); } else { this.fromObject(data); } } /** * Update the packet from a string of JSON * * @param {string} data - A string of text */ fromString(data) { try { let json = JSON.parse(data); Object.assign(this, json); } catch (e) { throw Error(`Malformed packet: ${e.message}`); } } /** * Update the packet from an Object, using and intermediate call to * JSON.stringify() to deep-copy the object, avoiding reference entanglement * * @param {string} data - An object */ fromObject(data) { try { let json = JSON.parse(JSON.stringify(data)); Object.assign(this, json); } catch (e) { throw Error(`Malformed packet: ${e.message}`); } } [Symbol.toPrimitive](hint) { this.id = Date.now(); switch (hint) { case 'string': return `${JSON.stringify(this)}\n`; case 'number': return `${JSON.stringify(this)}\n`.length; default: return true; } } toString() { return `${this}`; } }; /** * Data Channel */ var Channel = class Channel { constructor(params) { Object.assign(this, params); } get cancellable() { if (this._cancellable === undefined) { this._cancellable = new Gio.Cancellable(); } return this._cancellable; } get service() { return Gio.Application.get_default(); } get type() { return null; } get uuid() { if (this._uuid === undefined) { this._uuid = GLib.uuid_string_random(); } return this._uuid; } set uuid(uuid) { this._uuid = uuid; } /** * Override this in subclasses to configure any necessary socket options. * The defau;t implementation returns the original Gio.SocketConnection. */ _initSocket(connection) { return connection; } /** * Read the identity packet from the new connection * * @param {Gio.SocketConnection} connection - An unencrypted socket * @return {Gio.SocketConnection} - The connection after success */ _receiveIdent(connection) { return new Promise((resolve, reject) => { let stream = new Gio.DataInputStream({ base_stream: connection.input_stream, close_base_stream: false }); stream.read_line_async( GLib.PRIORITY_DEFAULT, this.cancellable, (stream, res) => { try { let data = stream.read_line_finish_utf8(res)[0]; stream.close(null); // Store the identity as an object property this.identity = new Packet(data); // Reject connections without a deviceId if (!this.identity.body.deviceId) { throw new Error('missing deviceId'); } resolve(connection); } catch (e) { reject(e); } } ); }); } /** * Write our identity packet to the new connection * * @param {Gio.SocketConnection} connection - An unencrypted socket * @return {Gio.SocketConnection} - The connection after success */ _sendIdent(connection) { return new Promise((resolve, reject) => { connection.output_stream.write_all_async( `${this.service.identity}`, GLib.PRIORITY_DEFAULT, this.cancellable, (stream, res) => { try { stream.write_all_finish(res); resolve(connection); } catch (e) { reject(e); } } ); }); } /** * Override these in subclasses to negotiate encryption. The default * implementations simply return the original Gio.SocketConnection. */ _clientEncryption(connection) { return Promise.resolve(connection); } _serverEncryption(connection) { return Promise.resolve(connection); } /** * Attach to @device as the default channel used for packet exchange. This * should connect the channel's Gio.Cancellable to mark the device as * disconnected, setup the IO streams, start the receive() loop and set the * device as connected. * * @param {Device.Device} device - The device to attach to */ attach(device) { // Detach any existing channel if (device._channel && device._channel !== this) { device._channel.cancellable.disconnect(device._channel._id); device._channel.close(); } // Attach the new channel and parse it's identity device._channel = this; this._id = this.cancellable.connect(device._setDisconnected.bind(device)); device._handleIdentity(this.identity); // Setup streams for packet exchange this.input_stream = new Gio.DataInputStream({ base_stream: this._connection.input_stream }); this.output_queue = []; this.output_stream = this._connection.output_stream; // Start listening for packets this.receive(device); // Emit connected:: if necessary if (!device.connected) { device._setConnected(); } } /** * Open an outgoing connection * * @param {Gio.SocketConnection} connection - The remote connection * @return {Boolean} - %true on connected, %false otherwise */ async open(connection) { try { this._connection = await this._initSocket(connection); this._connection = await this._sendIdent(this._connection); this._connection = await this._serverEncryption(this._connection); } catch (e) { this.close(); return Promise.reject(e); } } /** * Accept an incoming connection * * @param {Gio.TcpConnection} connection - The incoming connection */ async accept(connection) { try { this._connection = await this._initSocket(connection); this._connection = await this._receiveIdent(this._connection); this._connection = await this._clientEncryption(this._connection); } catch (e) { this.close(); return Promise.reject(e); } } /** * Close all streams associated with this channel, silencing any errors */ close() { debug(`${this.constructor.name} (${this.type})`); // Cancel any queued operations this.cancellable.cancel(); // Close any streams [this._connection, this.input_stream, this.output_stream].map(stream => { try { stream.close(null); } catch (e) { // Silence errors } }); } /** * Receive a packet from the channel and call receivePacket() on the device * * @param {Device.Device} device - The device which will handle the packet */ receive(device) { this.input_stream.read_line_async( GLib.PRIORITY_DEFAULT, this.cancellable, (stream, res) => { let data, packet; try { // Try to read and parse a packet data = stream.read_line_finish_utf8(res)[0]; // Queue another receive() before handling the packet this.receive(device); // In case %null is returned we don't want an error thrown // when trying to parse it as a packet if (data !== null) { packet = new Packet(data); debug(packet, this.identity.body.deviceName); device.receivePacket(packet); } } catch (e) { debug(e, this.identity.body.deviceName); this.close(); } } ); } /** * Send a packet to a device * * @param {object} packet - An dictionary of packet data */ async send(packet) { let next; try { this.output_queue.push(new Packet(packet)); if (!this.__lock) { this.__lock = true; while ((next = this.output_queue.shift())) { await new Promise((resolve, reject) => { this.output_stream.write_all_async( next.toString(), GLib.PRIORITY_DEFAULT, this.cancellable, (stream, res) => { try { resolve(stream.write_all_finish(res)); } catch (e) { reject(e); } } ); }); debug(next, this.identity.body.deviceName); } this.__lock = false; } } catch (e) { debug(e, this.identity.body.deviceName); this.close(); } } /** * Override these in subclasses to negotiate payload transfers. Both methods * should cleanup after themselves and return a success boolean. * * The default implementation will always report failure, for protocols that * won't or don't yet support payload transfers. */ async download(packet) { let result = false; try { throw new GObject.NotImplementedError(); } catch (e) { debug(e, this.identity.body.deviceName); } finally { this.close(); } return result; } async upload(port) { let result = false; try { throw new GObject.NotImplementedError(); } catch (e) { debug(e, this.identity.body.deviceName); } finally { this.close(); } return result; } /** * Transfer using g_output_stream_splice() * * @return {Boolean} - %true on success, %false on failure. */ async _transfer() { let result = false; try { result = await new Promise((resolve, reject) => { this.output_stream.splice_async( this.input_stream, Gio.OutputStreamSpliceFlags.NONE, GLib.PRIORITY_DEFAULT, this.cancellable, (source, res) => { try { if (source.splice_finish(res) < this.size) { throw new Error('incomplete data'); } resolve(true); } catch (e) { reject(e); } } ); }); } catch (e) { debug(e, this.identity.body.deviceName); } finally { this.close(); } return result; } }; /** * File Transfer base class */ var Transfer = class Transfer extends Channel { /** * @param {object} params - Transfer parameters * @param {Device.Device} params.device - The device that owns this transfer * @param {Gio.InputStream} params.input_stream - The input stream (read) * @param {Gio.OutputStrea} params.output_stream - The output stream (write) * @param {number} params.size - The size of the transfer in bytes */ constructor(params) { super(params); this.device._transfers.set(this.uuid, this); } get identity() { return this.device._channel.identity; } get type() { return 'transfer'; } close() { this.device._transfers.delete(this.uuid); super.close(); } }; gnome-shell-extension-gsconnect-20/src/service/daemon.js000077500000000000000000000575651341554142200235600ustar00rootroot00000000000000#!/usr/bin/env gjs 'use strict'; const Gettext = imports.gettext.domain('org.gnome.Shell.Extensions.GSConnect'); const _ = Gettext.gettext; const System = imports.system; imports.gi.versions.Atspi = '2.0'; imports.gi.versions.Gdk = '3.0'; imports.gi.versions.GdkPixbuf = '2.0'; imports.gi.versions.Gio = '2.0'; imports.gi.versions.GIRepository = '2.0'; imports.gi.versions.GLib = '2.0'; imports.gi.versions.GObject = '2.0'; imports.gi.versions.Gtk = '3.0'; imports.gi.versions.Pango = '1.0'; imports.gi.versions.UPowerGlib = '1.0'; const Gdk = imports.gi.Gdk; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; // Find the root datadir of the extension function get_datadir() { let m = /@(.+):\d+/.exec((new Error()).stack.split('\n')[1]); return Gio.File.new_for_path(m[1]).get_parent().get_parent().get_path(); } window.gsconnect = {extdatadir: get_datadir()}; imports.searchPath.unshift(gsconnect.extdatadir); imports._gsconnect; // Local Imports const Bluetooth = imports.service.bluetooth; const Core = imports.service.core; const Device = imports.service.device; const Lan = imports.service.lan; const ServiceUI = imports.service.ui.service; const Settings = imports.service.ui.settings; const _GITHUB = 'https://github.com/andyholmes/gnome-shell-extension-gsconnect'; const Service = GObject.registerClass({ GTypeName: 'GSConnectService', Properties: { 'devices': GObject.param_spec_variant( 'devices', 'Devices', 'A list of known devices', new GLib.VariantType('as'), null, GObject.ParamFlags.READABLE ), 'discoverable': GObject.ParamSpec.boolean( 'discoverable', 'Discoverable', 'Whether the service responds to discovery requests', GObject.ParamFlags.READWRITE, false ), 'name': GObject.ParamSpec.string( 'name', 'deviceName', 'The name announced to the network', GObject.ParamFlags.READWRITE, 'GSConnect' ), 'type': GObject.ParamSpec.string( 'type', 'deviceType', 'The service device type', GObject.ParamFlags.READABLE, 'desktop' ) } }, class Service extends Gtk.Application { _init() { super._init({ application_id: gsconnect.app_id, flags: Gio.ApplicationFlags.HANDLES_OPEN }); // FIXME: Breaks Multi-DPI support. Remove once a Wayland protocol is // created or an interface can be exported from gnome-shell process. // FIXME: Removing this causes a regression of #307. Gdk.set_allowed_backends('x11,*'); GLib.set_prgname(gsconnect.app_id); GLib.set_application_name('GSConnect'); // Track devices with id as key this._devices = new Map(); // Properties gsconnect.settings.bind('discoverable', this, 'discoverable', 0); gsconnect.settings.bind('public-name', this, 'name', 0); } get certificate() { if (this._certificate === undefined) { this._certificate = Gio.TlsCertificate.new_for_paths( GLib.build_filenamev([gsconnect.configdir, 'certificate.pem']), GLib.build_filenamev([gsconnect.configdir, 'private.pem']) ); } return this._certificate; } get devices() { return Array.from(this._devices.keys()); } get fingerprint() { return this.certificate.fingerprint(); } get identity() { if (this._identity === undefined) { this._identity = new Core.Packet({ id: 0, type: 'kdeconnect.identity', body: { deviceId: this.certificate.common_name, deviceName: this.name, deviceType: this.type, tcpPort: 1716, protocolVersion: 7, incomingCapabilities: [], outgoingCapabilities: [] } }); for (let name in imports.service.plugins) { // Don't report 'mousepad' support in Wayland sessions if (_WAYLAND && name === 'mousepad') continue; let meta = imports.service.plugins[name].Metadata; if (!meta) continue; meta.incomingCapabilities.map(type => { this._identity.body.incomingCapabilities.push(type); }); meta.outgoingCapabilities.map(type => { this._identity.body.outgoingCapabilities.push(type); }); } } return this._identity; } get type() { if (this._type === undefined) { try { let type = GLib.file_get_contents('/sys/class/dmi/id/chassis_type')[1]; if (type instanceof Uint8Array) { type = imports.byteArray.toString(type); } type = Number(type); this._type = [8, 9, 10, 14].includes(type) ? 'laptop' : 'desktop'; } catch (e) { this._type = 'desktop'; } } return this._type; } /** * Send identity to @address or broadcast if %null * * @param {string|Gio.InetSocketAddress} - TCP address, bluez path or %null */ broadcast(address = null) { try { switch (true) { case (address instanceof Gio.InetSocketAddress): this.lan.broadcast(address); break; case (typeof address === 'string'): this.bluetooth.broadcast(address); break; // If not discoverable we'll only broadcast to paired devices case !this.discoverable: this.reconnect(); break; // We only do true "broadcasts" for LAN default: this.lan.broadcast(); } } catch (e) { logError(e); } } /** * Try to reconnect to each paired device that has disconnected */ reconnect() { for (let [id, device] of this._devices.entries()) { if (!device.connected) { if (device.paired) { device.activate(); // Prune the device if the settings window is not open } else if (!this._window || !this._window.visible) { device.destroy(); this._devices.delete(id); gsconnect.settings.set_strv('devices', this.devices); this.notify('devices'); } } } return GLib.SOURCE_CONTINUE; } /** * Return a device for @packet, creating it and adding it to the list of * of known devices if it doesn't exist. * * @param {kdeconnect.identity} packet - An identity packet for the device * @return {Device.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 let unpaired = Array.from(this._devices.values()).filter(dev => { return !dev.paired; }); if (unpaired.length === 3 && this.discoverable) { this.activate_action('discoverable', null); let error = new Error(); error.name = 'DiscoveryWarning'; this.notify_error(error); } device = new Device.Device(packet); this._devices.set(device.id, device); gsconnect.settings.set_strv('devices', this.devices); this.notify('devices'); device.loadPlugins(); } return device; } /** * Delete a known device. * * Removes the device from the list of known devices, unpairs it, destroys * it and deletes all GSettings and cached files. * * @param {String} id - The id of the device to delete */ deleteDevice(id) { let device = this._devices.get(id); if (device) { // Stash the settings path before unpairing and removing let settings_path = device.settings.path; device.sendPacket({type: 'kdeconnect.pair', pair: 'false'}); // device.destroy(); this._devices.delete(id); // Delete all GSettings GLib.spawn_command_line_async(`dconf reset -f ${settings_path}`); // Delete the cache let cache = GLib.build_filenamev([gsconnect.cachedir, id]); Gio.File.rm_rf(cache); // Notify gsconnect.settings.set_strv('devices', this.devices); this.notify('devices'); } } /** * Service GActions */ _initActions() { let actions = [ ['broadcast', this.broadcast.bind(this)], ['devel', this._devel.bind(this)], ['device', this._device.bind(this), '(ssbv)'], ['error', this._error.bind(this), 'a{ss}'], ['settings', this._settings.bind(this)], ['wiki', this._wiki.bind(this), 's'] ]; for (let [name, callback, type] of actions) { let action = new Gio.SimpleAction({ name: name, parameter_type: (type) ? new GLib.VariantType(type) : null }); action.connect('activate', callback); this.add_action(action); } this.add_action(gsconnect.settings.create_action('discoverable')); this.set_accels_for_action('app.wiki::Help', ['F1']); } /** * 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 - ... * @param {GLib.Variant(av)} parameter - ... * @param {GLib.Variant(s)} parameter[0] - Device Id or '*' for all * @param {GLib.Variant(s)} parameter[1] - GAction name * @param {GLib.Variant(b)} parameter[2] - %false if the parameter is null * @param {GLib.Variant(v)} parameter[3] - GAction parameter */ _device(action, parameter) { parameter = parameter.unpack(); let id = parameter[0].unpack(); let devices = (id === '*') ? this._devices.values() : [this._devices.get(id)]; for (let device of devices) { // If the device is available if (device) { device.activate_action( parameter[1].unpack(), parameter[2].unpack() ? parameter[3].unpack() : null ); } } } _devel() { (new imports.service.ui.devel.Window()).present(); } _error(action, parameter) { try { let error = parameter.deep_unpack(); let dialog = new Gtk.MessageDialog({ text: error.message, secondary_text: error.stack, buttons: Gtk.ButtonsType.CLOSE, message_type: Gtk.MessageType.ERROR, }); dialog.add_button(_('Report'), Gtk.ResponseType.OK); dialog.set_keep_above(true); let [message, stack] = dialog.get_message_area().get_children(); message.halign = Gtk.Align.START; message.selectable = true; stack.selectable = true; dialog.connect('response', (dialog, response_id) => { if (response_id === Gtk.ResponseType.OK) { let query = encodeURIComponent(dialog.text).replace('%20', '+'); this._github(`issues?q=is%3Aissue+"${query}"`); } else { dialog.destroy(); } }); dialog.show(); } catch (e) { logError(e); } } _settings(page = null, parameter = null) { if (parameter instanceof GLib.Variant) { page = parameter.unpack(); } if (!this._window) { this._window = new Settings.Window(); } // Open to a specific page if (typeof page === 'string' && this._window.stack.get_child_by_name(page)) { this._window._onDeviceSelected(page); // Open the main page } else { this._window._onPrevious(); } this._window.present(); } _wiki(action, parameter) { this._github(`wiki/${parameter.unpack()}`); } _github(path = []) { let uri = [_GITHUB].concat(path.split('/')).join('/'); Gio.AppInfo.launch_default_for_uri_async(uri, null, null, (src, res) => { try { Gio.AppInfo.launch_default_for_uri_finish(res); } catch (e) { logError(e); } }); } /** * Override Gio.Application.send_notification() to respect donotdisturb */ send_notification(id, notification) { if (!this._notificationSettings) { this._notificationSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.notifications.application', path: '/org/gnome/desktop/notifications/application/org-gnome-shell-extensions-gsconnect/' }); } let now = GLib.DateTime.new_now_local().to_unix(); let dnd = (gsconnect.settings.get_int('donotdisturb') <= now); // TODO: Maybe the 'enable-sound-alerts' should be left alone/queried this._notificationSettings.set_boolean('enable-sound-alerts', dnd); this._notificationSettings.set_boolean('show-banners', dnd); super.send_notification(id, notification); } /** * 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 */ remove_notification(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); } } ); } /** * Report a service-level error * * @param {object} error - An Error or object with name, message and stack * @param {string} context - The scope of the error */ notify_error(error) { try { // Always log the error logError(error); // Create an new notification let id, title, body, icon, priority, time; let notif = new Gio.Notification(); switch (error.name) { // A TLS certificate failure case 'AuthenticationError': id = `"${error.deviceName}"@${error.deviceHost}`; title = _('Authentication Failure'); time = GLib.DateTime.new_now_local().format('%F %R'); body = `"${error.deviceName}"@${error.deviceHost} (${time})`; icon = new Gio.ThemedIcon({name: 'dialog-error'}); priority = Gio.NotificationPriority.URGENT; break; case 'LanError': case 'ProxyError': id = error.name; title = _('Network Error'); body = _('Click for help troubleshooting'); icon = new Gio.ThemedIcon({name: 'network-error'}); priority = Gio.NotificationPriority.URGENT; notif.set_default_action(`app.wiki('Help#${error.name}')`); break; case 'DiscoveryWarning': id = 'discovery-warning'; title = _('Discovery Disabled'); body = _('Discovery has been disabled due to the number of devices on this network.'); icon = new Gio.ThemedIcon({name: 'dialog-warning'}); priority = Gio.NotificationPriority.NORMAL; notif.set_default_action('app.settings'); break; case 'PluginError': id = `${error.plugin}-error`; title = _('%s Plugin Failed To Load').format(error.plugin); body = _('Click for more information'); icon = new Gio.ThemedIcon({name: 'dialog-error'}); priority = Gio.NotificationPriority.HIGH; error = new GLib.Variant('a{ss}', { name: error.name.trim(), message: error.message.trim(), stack: error.stack.trim() }); notif.set_default_action_and_target('app.error', error); break; default: id = `${Date.now()}`; title = error.name.trim(); body = _('Click for more information'); icon = new Gio.ThemedIcon({name: 'dialog-error'}); error = new GLib.Variant('a{ss}', { name: error.name.trim(), message: error.message.trim(), stack: error.stack.trim() }); notif.set_default_action_and_target('app.error', error); priority = Gio.NotificationPriority.HIGH; } // Create an urgent notification notif.set_title(`GSConnect: ${title}`); notif.set_body(body); notif.set_icon(icon); notif.set_priority(priority); // Bypass override super.send_notification(id, notif); } catch (e) { logError(e); } } /** * Load each script in components/ and instantiate a Service if it has one */ _loadComponents() { for (let name in imports.service.components) { try { let module = imports.service.components[name]; if (module.hasOwnProperty('Service')) { this[name] = new module.Service(); } } 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( gsconnect.extdatadir + '/service/daemon.js' ).monitor( Gio.FileMonitorFlags.WATCH_MOVES, null ); this._serviceMonitor.connect('changed', () => this.quit()); // Init some resources let provider = new Gtk.CssProvider(); provider.load_from_resource(gsconnect.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 { let appInfo = Gio.DesktopAppInfo.new(`${gsconnect.app_id}.desktop`); appInfo.add_supports_type('x-scheme-handler/sms'); appInfo.add_supports_type('x-scheme-handler/tel'); } catch (e) { warning(e); } // Keep identity updated and broadcast any name changes gsconnect.settings.connect('changed::public-name', (settings) => { this.identity.body.deviceName = this.name; }); // GActions this._initActions(); // Components (PulseAudio, UPower, etc) this._loadComponents(); // Lan.ChannelService try { this.lan = new Lan.ChannelService(); } catch (e) { e.name = 'LanError'; this.notify_error(e); } // Bluetooth.ChannelService try { //this.bluetooth = new Bluetooth.ChannelService(); } catch (e) { if (this.bluetooth) { this.bluetooth.destroy(); } } GLib.timeout_add_seconds(300, 5, this.reconnect.bind(this)); } vfunc_dbus_register(connection, object_path) { this.objectManager = new Gio.DBusObjectManagerServer({ connection: connection, object_path: object_path }); // Load cached devices for (let id of gsconnect.settings.get_strv('devices')) { let device = new Device.Device({body: {deviceId: id}}); this._devices.set(id, device); device.loadPlugins(); } return true; } vfunc_open(files, hint) { super.vfunc_open(files, hint); for (let 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: warning(`Unsupported URI: ${file.get_uri()}`); return; } // Show chooser dialog new ServiceUI.DeviceChooserDialog({ title: title, action: action, parameter: parameter }); } catch (e) { logError(e, `GSConnect: Opening ${file.get_uri()}:`); } } } vfunc_shutdown() { // Destroy the channel providers first to avoid any further connections try { if (this.lan) this.lan.destroy(); } catch (e) { debug(e); } try { if (this.bluetooth) this.bluetooth.destroy(); } catch (e) { debug(e); } // This must be done before ::dbus-unregister is emitted this._devices.forEach(device => device.destroy()); // Destroy the remaining components last try { if (this.mpris) this.mpris.destroy(); } catch (e) { debug(e); } try { if (this.notification) this.notification.destroy(); } catch (e) { debug(e); } // Chain up last (application->priv->did_shutdown) super.vfunc_shutdown(); } }); (new Service()).run([System.programInvocationName].concat(ARGV)); gnome-shell-extension-gsconnect-20/src/service/device.js000066400000000000000000000630631341554142200235370ustar00rootroot00000000000000'use strict'; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const Core = imports.service.core; const DBus = imports.service.components.dbus; const Lan = imports.service.lan; const Bluetooth = imports.service.bluetooth; const UUID = 'org.gnome.Shell.Extensions.GSConnect.Device'; const INTERFACE_INFO = gsconnect.dbusinfo.lookup_interface(UUID); /** * An object representing a remote device. * * Device class is subclassed from Gio.SimpleActionGroup so it implements the * GActionGroup and GActionMap interfaces, like Gio.Application. * * TODO... */ var Device = GObject.registerClass({ GTypeName: 'GSConnectDevice', Properties: { 'connected': GObject.ParamSpec.boolean( 'connected', 'Connected', 'Whether the device is connected', GObject.ParamFlags.READABLE, null ), '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', '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.SimpleActionGroup { _init(identity) { super._init(); this._channel = null; this._connected = false; // GLib.Source timeout id's for pairing requests this._incomingPairRequest = 0; this._outgoingPairRequest = 0; // Maps of name->Plugin, packet->Plugin, uuid->Transfer this._plugins = new Map(); this._handlers = new Map(); this._transfers = new Map(); // We at least need the device Id for GSettings and the DBus interface let deviceId = identity.body.deviceId; // GSettings this.settings = new Gio.Settings({ settings_schema: gsconnect.gschema.lookup(UUID, true), path: `/org/gnome/shell/extensions/gsconnect/device/${deviceId}/` }); // Watch for plugins changes this._disabledPluginsId = this.settings.connect( 'changed::disabled-plugins', this._onDisabledPlugins.bind(this) ); // Parse identity if initialized with a proper packet if (identity.id !== undefined) { this._handleIdentity(identity); } // Export an object path for the device this._dbus_object = new Gio.DBusObjectSkeleton({ g_object_path: this.object_path }); this.service.objectManager.export(this._dbus_object); // Export the Device interface this._dbus = new DBus.Interface({ g_instance: this, g_interface_info: INTERFACE_INFO }); this._dbus_object.add_interface(this._dbus); // GActions/GMenu this._actionsId = Gio.DBus.session.export_action_group( this.object_path, this ); this._registerActions(); this.menu = new Gio.Menu(); this._menuId = Gio.DBus.session.export_menu_model( this.object_path, this.menu ); } /** Device Properties */ get connected () { return this._connected; } get connection_type() { if (this._channel !== null) { return this._channel.type; } return this.settings.get_string('last-connection'); } get contacts() { let contacts = this.lookup_plugin('contacts'); if (contacts && contacts.settings.get_boolean('contacts-source')) { return contacts._store; } else { return this.service.contacts; } } // TODO: should we just store the fingerprint instead of the pem? get encryption_info() { let fingerprint = _('Not available'); // 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( this.settings.get_string('bluetooth-host') ); // If the device is connected use the certificate from the connection } else if (this.connected) { fingerprint = this._channel.certificate.fingerprint(); // Otherwise pull it out of the settings } else if (this.paired) { fingerprint = Gio.TlsCertificate.new_from_pem( this.settings.get_string('certificate-pem'), -1 ).fingerprint(); } // TRANSLATORS: Label for TLS Certificate fingerprint // // Example: // // Google Pixel Fingerprint: // 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 return _('%s Fingerprint:').format(this.name) + '\n' + fingerprint + '\n\n' + _('%s Fingerprint:').format(this.service.name) + '\n' + this.service.fingerprint; } get id() { return this.settings.get_string('id'); } get name() { return this.settings.get_string('name'); } get paired() { return this.settings.get_boolean('paired'); } get supported_plugins() { let supported = this.settings.get_strv('supported-plugins'); // Preempt 'mousepad' plugin on Wayland if (_WAYLAND) supported.splice(supported.indexOf('mousepad'), 1); return supported; } get allowed_plugins() { let disabled = this.settings.get_strv('disabled-plugins'); return this.supported_plugins.filter(name => !disabled.includes(name)); } get icon_name() { switch (this.type) { case 'laptop': return 'laptop'; case 'phone': return 'smartphone'; case 'tablet': return 'tablet'; default: return 'computer'; } } get service() { return Gio.Application.get_default(); } get type() { return this.settings.get_string('type'); } get display_type() { switch (this.type) { case 'laptop': return _('Laptop'); case 'phone': return _('Smartphone'); case 'tablet': return _('Tablet'); default: return _('Desktop'); } } get object_path() { return `${gsconnect.app_path}/Device/${this.id.replace(/\W+/g, '_')}`; } _handleIdentity(packet) { this.settings.set_string('id', packet.body.deviceId); this.settings.set_string('name', packet.body.deviceName); this.settings.set_string('type', packet.body.deviceType); if (packet.body.hasOwnProperty('bluetoothHost')) { this.settings.set_string('bluetooth-host', packet.body.bluetoothHost); this.settings.set_string('bluetooth-path', packet.body.bluetoothPath); this.settings.set_string('last-connection', 'bluetooth'); } else if (packet.body.hasOwnProperty('tcpHost')) { this.settings.set_string('tcp-host', packet.body.tcpHost); this.settings.set_uint('tcp-port', packet.body.tcpPort); this.settings.set_string('last-connection', 'tcp'); } this.settings.set_strv( 'incoming-capabilities', packet.body.incomingCapabilities.sort() ); this.settings.set_strv( 'outgoing-capabilities', packet.body.outgoingCapabilities.sort() ); let supported = []; for (let name in imports.service.plugins) { let meta = imports.service.plugins[name].Metadata; if (!meta) continue; // If we can handle packets it sends... if (meta.incomingCapabilities.some(t => packet.body.outgoingCapabilities.includes(t))) { supported.push(name); // ...or we send packets it can handle } else if (meta.outgoingCapabilities.some(t => packet.body.incomingCapabilities.includes(t))) { supported.push(name); } } this.settings.set_strv('supported-plugins', supported.sort()); } /** * This is invoked by Core.Channel.attach() which also sets this._channel */ _setConnected() { debug(`Connected to ${this.name} (${this.id})`); this.settings.set_string('last-connection', this._channel.type); this._connected = true; this.notify('connected'); this._plugins.forEach(async (plugin) => plugin.connected()); } /** * This is the callback for the Core.Channel's cancellable object */ _setDisconnected() { debug(`Disconnected from ${this.name} (${this.id})`); this._channel = null; this._connected = false; this.notify('connected'); this._plugins.forEach(async (plugin) => plugin.disconnected()); } /** * Request a connection from the device */ activate() { let lastConnection = this.settings.get_string('last-connection'); // If the same channel type is currently open bail... if (this._channel !== null && this.connection_type === lastConnection) { debug(`${this.name}: ${lastConnection} connection already active`); return; } else if (lastConnection === 'bluetooth') { this.service.broadcast(this.settings.get_string('bluetooth-path')); } else { let tcpAddress = Gio.InetSocketAddress.new_from_string( this.settings.get_string('tcp-host'), this.settings.get_uint('tcp-port') ); this.service.broadcast(tcpAddress); } } /** * Receive a packet from the attached channel and route it to its handler * * @param {Core.Packet} packet - The incoming packet object */ receivePacket(packet) { try { let handler = this._handlers.get(packet.type); switch (true) { // We handle pair requests case (packet.type === 'kdeconnect.pair'): this._handlePair(packet); break; // The device must think we're paired; inform it we are not case !this.paired: this.unpair(); break; // This is a supported packet case (handler !== undefined): handler.handlePacket(packet); break; // This is an unsupported packet or disabled plugin default: throw new Error(`Unsupported packet type (${packet.type})`); } } catch (e) { warning(e, this.name); } } /** * Send a packet to the device * @param {Object} packet - An object of packet data... * @param {Gio.Stream} payload - A payload stream // TODO */ sendPacket(packet, payload = null) { try { if (this.connected && (this.paired || packet.type === 'kdeconnect.pair')) { this._channel.send(packet); } } catch (e) { logError(e, this.name); } } /** * Actions */ _registerActions() { // Stock device actions let activate = new Gio.SimpleAction({ name: 'activate', state: new GLib.Variant('(ss)', [_('Reconnect'), 'view-refresh-symbolic']), }); activate.connect('activate', this.activate.bind(this)); this.add_action(activate); let openSettings = new Gio.SimpleAction({ name: 'openSettings', state: new GLib.Variant('(ss)', [_('Settings'), 'preferences-system-symbolic']) }); openSettings.connect('activate', this.openSettings.bind(this)); this.add_action(openSettings); // Pairing notification actions let acceptPair = new Gio.SimpleAction({name: 'pair'}); acceptPair.connect('activate', this.pair.bind(this)); this.add_action(acceptPair); let rejectPair = new Gio.SimpleAction({name: 'unpair'}); rejectPair.connect('activate', this.unpair.bind(this)); this.add_action(rejectPair); // Transfer notification actions let cancelTransfer = new Gio.SimpleAction({ name: 'cancelTransfer', parameter_type: new GLib.VariantType('s') }); cancelTransfer.connect('activate', this.cancelTransfer.bind(this)); this.add_action(cancelTransfer); let openPath = new Gio.SimpleAction({ name: 'openPath', parameter_type: new GLib.VariantType('s') }); openPath.connect('activate', this.openPath); this.add_action(openPath); } /** * Hide a notification, device analog for GApplication.withdraw_notification() * * @param {string} id - Id for the notification to withdraw */ hideNotification(id) { this.service.withdraw_notification(`${this.id}|${id}`); } /** * Show a notification, device analog for GApplication.send_notification() */ showNotification(params) { 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); let 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) { let 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 (let button of params.buttons) { let 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); } /** * File Transfers */ cancelTransfer(action, parameter) { let uuid = parameter.unpack(); let transfer = this._transfers.get(uuid); if (transfer !== undefined) { this._transfers.delete(uuid); transfer.close(); } } createTransfer(params) { params.device = this; switch (this.connection_type) { case 'tcp': return new Lan.Transfer(params); case 'bluetooth': return new Bluetooth.Transfer(params); // Fallback to returning a mock transfer that always appears to fail default: return { uuid: 'mock-transfer', download: () => false, upload: () => false }; } } openPath(action, parameter) { let path = parameter.unpack(); // Normalize paths to URIs, assuming local file path = path.includes('://') ? path : `file://${path}`; Gio.AppInfo.launch_default_for_uri_async(path, null, null, (src, res) => { try { Gio.AppInfo.launch_default_for_uri_finish(res); } catch (e) { logError(e); } }); } /** * 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) { debug(`Pair accepted by ${this.name}`); this._setPaired(true); this.loadPlugins(); // The device thinks we're unpaired } else if (this.paired) { this._setPaired(true); this.pair(); this.loadPlugins(); // The device is requesting pairing } else { debug(`Pair request from ${this.name}`); this._notifyPairRequest(); } // Device is requesting unpairing/rejecting our request } else { debug(`Pair rejected by ${this.name}`); this._setPaired(false); this.unloadPlugins(); } } /** * Notify the user of an incoming pair request and set a 30s timeout */ _notifyPairRequest() { 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._resetPairRequest(); this._incomingPairRequest = GLib.timeout_add_seconds( GLib.PRIORITY_DEFAULT, 30, this._setPaired.bind(this, false) ); } /** * Reset pair request timeouts and withdraw any notifications */ _resetPairRequest() { if (this._incomingPairRequest) { this.hideNotification('pair-request'); GLib.source_remove(this._incomingPairRequest); this._incomingPairRequest = 0; } if (this._outgoingPairRequest) { GLib.source_remove(this._outgoingPairRequest); this._outgoingPairRequest = 0; } } /** * Set the internal paired state of the device and emit ::notify * * @param {Boolean} bool - The paired state to set */ _setPaired(bool) { this._resetPairRequest(); // For TCP connections we store or reset the TLS Certificate if (this.connection_type === 'tcp') { if (bool) { this.settings.set_string( 'certificate-pem', this._channel.certificate.certificate_pem ); } else { this.settings.reset('certificate-pem'); } } this.settings.set_boolean('paired', bool); this.notify('paired'); } /** * Send or accept an incoming pair request; also exported as a GAction */ pair() { // We're accepting an incoming pair request... if (this._incomingPairRequest) { // so set the paired state to true... this._setPaired(true); // then loop back around to send confirmation... this.pair(); // ...before loading plugins this.loadPlugins(); return; } // Send a pair packet this.sendPacket({ id: 0, type: 'kdeconnect.pair', body: {pair: true} }); // We're initiating an outgoing pair request if (!this.paired) { this._resetPairRequest(); this._outgoingPairRequest = GLib.timeout_add_seconds( GLib.PRIORITY_DEFAULT, 30, this._setPaired.bind(this, false) ); debug(`Pair request sent to ${this.name}`); } } /** * Unpair or reject an incoming pair request; also exported as a GAction */ unpair() { debug(`${this.name} (${this.id})`); // Send the unpair packet only if we're connected if (this.connected) { this.sendPacket({ id: 0, type: 'kdeconnect.pair', body: {pair: false} }); } this._setPaired(false); this.unloadPlugins(); } /** * Plugin Functions */ get_incoming_supported(type) { let incoming = this.settings.get_strv('incoming-capabilities'); return incoming.includes(`kdeconnect.${type}`); } get_outgoing_supported(type) { let outgoing = this.settings.get_strv('outgoing-capabilities'); return outgoing.includes(`kdeconnect.${type}`); } get_plugin_supported(name) { return this.supported_plugins.includes(name); } get_plugin_allowed(name) { return this.allowed_plugins.includes(name); } lookup_plugin(name) { return this._plugins.get(name) || null; } _onDisabledPlugins(settings) { let disabled = this.settings.get_strv('disabled-plugins'); disabled.map(name => this.unloadPlugin(name)); this.allowed_plugins.map(name => this.loadPlugin(name)); // Make sure we're change the contacts store if the plugin was disabled if (!this.get_plugin_allowed('contacts')) { this.notify('contacts'); } } loadPlugin(name) { let handler, plugin; try { if (this.paired && !this._plugins.has(name)) { debug(`loading '${name}' plugin`, this.name); // Instantiate the handler handler = imports.service.plugins[name]; plugin = new handler.Plugin(this); // Register packet handlers for (let packetType of handler.Metadata.incomingCapabilities) { this._handlers.set(packetType, plugin); } // Register plugin this._plugins.set(name, plugin); // Run the connected()/disconnected() handler this.connected ? plugin.connected() : plugin.disconnected(); } } catch (e) { e.name = (e.name === 'Error') ? 'PluginError' : e.name; e.plugin = handler.Metadata.label; this.service.notify_error(e); } } async loadPlugins() { for (let name of this.allowed_plugins) { await this.loadPlugin(name); } } unloadPlugin(name) { let handler, plugin; try { if (this._plugins.has(name)) { debug(`unloading '${name}' plugin`, this.name); // Unregister packet handlers handler = imports.service.plugins[name]; plugin = this._plugins.get(name); for (let type of handler.Metadata.incomingCapabilities) { this._handlers.delete(type); } // Unregister plugin this._plugins.delete(name); plugin.destroy(); } } catch (e) { logError(e, `${this.name}: unloading ${name}`); } } async unloadPlugins() { for (let name of this._plugins.keys()) { await this.unloadPlugin(name); } } openSettings() { this.service._settings(this.id); } destroy() { // this.settings.disconnect(this._disabledPluginsId); // Close the channel if still connected if (this._channel !== null) { this._channel.close(); } // Synchronously destroy plugins this._plugins.forEach(plugin => plugin.destroy()); // Unexport GActions and GMenu Gio.DBus.session.unexport_action_group(this._actionsId); Gio.DBus.session.unexport_menu_model(this._menuId); // Unexport the Device interface and object this._dbus.flush(); this._dbus_object.remove_interface(this._dbus); this._dbus_object.flush(); this.service.objectManager.unexport(this._dbus_object.g_object_path); } }); gnome-shell-extension-gsconnect-20/src/service/lan.js000066400000000000000000000455501341554142200230530ustar00rootroot00000000000000'use strict'; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Core = imports.service.core; /** * TCP Port Constants */ const TCP_MIN_PORT = 1716; const TCP_MAX_PORT = 1764; const UDP_PORT = 1716; /** * One-time check for Linux/FreeBSD socket options */ var _LINUX_SOCKETS = false; try { // This should throw on FreeBSD // https://github.com/freebsd/freebsd/blob/master/sys/netinet/tcp.h#L159 new Gio.Socket({ family: Gio.SocketFamily.IPV4, protocol: Gio.SocketProtocol.TCP, type: Gio.SocketType.STREAM }).get_option(6, 5); // Otherwise we can use Linux socket options debug('Using Linux socket options'); _LINUX_SOCKETS = true; } catch (e) { debug('Using FreeBSD socket options'); _LINUX_SOCKETS = false; } /** * Lan.ChannelService consists of two parts. * * The TCP Listener listens on a port (usually 1716) and constructs a Channel * object from the incoming Gio.TcpConnection. * * The UDP Listener listens on a port 1716 for incoming JSON identity packets * which include the TCP port for connections, while the IP address is taken * from the UDP packet itself. We respond to incoming packets by opening a TCP * connection and broadcast outgoing packets to 255.255.255.255. */ var ChannelService = class ChannelService { constructor() { this.allowed = new Set(); this.connecting = new Map(); // Start TCP/UDP listeners this._initUdpListener(); this._initTcpListener(); // Monitor network changes this._networkMonitor = Gio.NetworkMonitor.get_default(); this._networkAvailable = this._networkMonitor.network_available; this._networkChangedId = this._networkMonitor.connect( 'network-changed', this._onNetworkChanged.bind(this) ); } get service() { return Gio.Application.get_default(); } _onNetworkChanged(monitor, network_available) { if (this._networkAvailable !== network_available) { this._networkAvailable = network_available; this.broadcast(); } } _initTcpListener() { this._tcp = new Gio.SocketService(); try { this._tcp.add_inet_port(TCP_MIN_PORT, null); } catch (e) { this._tcp.stop(); this._tcp.close(); // The UDP listener must have succeeded so shut it down, too this._udp_source.destroy(); this._udp_stream.close(null); this._udp.close(); throw e; } this._tcp.connect('incoming', this._onIncomingChannel.bind(this)); } async _onIncomingChannel(listener, connection) { let channel, host, device; try { channel = new Channel(); host = connection.get_remote_address().address.to_string(); // Cancel any connection still resolving with this host if (this.connecting.has(host)) { debug(`Cancelling current connection with ${host}`); this.connecting.get(host).close(); this.connecting.delete(host); } // Track this connection to avoid a race condition debug(`Accepting connection from ${host}`); this.connecting.set(host, channel); // Accept the connection await channel.accept(connection); channel.identity.body.tcpHost = host; channel.identity.body.tcpPort = '1716'; device = this.service._devices.get(channel.identity.body.deviceId); switch (true) { // An existing device case (device !== undefined): break; // A response to a "direct" broadcast, or we're discoverable case this.allowed.has(host): case this.service.discoverable: device = await this.service._ensureDevice(channel.identity); break; // ...otherwise bail default: throw Error('device not allowed'); } // Attach a device to the channel channel.attach(device); } catch (e) { debug(e); } finally { this.connecting.delete(host); } } _initUdpListener() { this._udp = new Gio.Socket({ family: Gio.SocketFamily.IPV4, type: Gio.SocketType.DATAGRAM, protocol: Gio.SocketProtocol.UDP, broadcast: true }); this._udp.init(null); try { let addr = new Gio.InetSocketAddress({ address: Gio.InetAddress.new_any(Gio.SocketFamily.IPV4), port: UDP_PORT }); this._udp.bind(addr, false); } catch (e) { this._udp.close(); throw e; } // Default broadcast address this._udp_address = Gio.InetSocketAddress.new_from_string( '255.255.255.255', UDP_PORT ); // Input stream this._udp_stream = new Gio.DataInputStream({ base_stream: new Gio.UnixInputStream({ fd: this._udp.fd, close_fd: false }) }); // Watch input socket for incoming packets this._udp_source = this._udp.create_source(GLib.IOCondition.IN, null); this._udp_source.set_callback(this._onIncomingIdentity.bind(this)); this._udp_source.attach(null); } async _onIncomingIdentity() { try { // Most of the datagram methods don't work in GJS, so we "peek" for // the host address first... let host = this._udp.receive_message( [], Gio.SocketMsgFlags.PEEK, null )[1].address.to_string(); // ...then read the packet from a stream, filling in the tcpHost let data = this._udp_stream.read_line_utf8(null)[0]; let packet = new Core.Packet(data); packet.body.tcpHost = host; // Bail if the deviceId is missing if (!packet.body.hasOwnProperty('deviceId')) { warning('missing deviceId', packet.body.deviceName); return; } // Silently ignore our own broadcasts if (packet.body.deviceId === this.service.identity.body.deviceId) { return; } debug(packet); let device = this.service._devices.get(packet.body.deviceId); switch (true) { // Proceed if this is an existing device... case (device !== undefined): break; // Or the service is discoverable or host is allowed... case this.service.discoverable: case this.allowed.has(host): device = this.service._ensureDevice(packet); break; // ...otherwise bail default: warning('device not allowed', packet.body.deviceName); return; } // Silently ignore broadcasts from connected devices, but update // from the identity packet if (device._channel !== null) { debug('already connected'); device._handleIdentity(packet); return; } // Create a new channel let channel = new Channel(); channel.identity = packet; let connection = await new Promise((resolve, reject) => { let address = Gio.InetSocketAddress.new_from_string( packet.body.tcpHost, packet.body.tcpPort ); let client = new Gio.SocketClient({enable_proxy: false}); client.connect_async(address, null, (client, res) => { try { resolve(client.connect_finish(res)); } catch (e) { reject(e); } }); }); // Connect the channel and attach it to the device on success await channel.open(connection); channel.attach(device); } catch (e) { logError(e); // Notify the user of Proxy errors if ([40, 41, 42, 43].includes(e.code)) { e.name = 'ProxyError'; this.service.notify_error(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) { debug('Network unavailable; aborting'); return; // Remember manual addresses so we know to accept connections } else if (address instanceof Gio.InetSocketAddress) { this.allowed.add(address.address.to_string()); // Only broadcast to the network if no address is specified } else { debug('Broadcasting to LAN'); address = this._udp_address; } this._udp.send_to(address, `${this.service.identity}`, null); } catch (e) { warning(e); } } destroy() { this._networkMonitor.disconnect(this._networkChangedId); this._tcp.stop(); this._tcp.close(); this._udp_source.destroy(); this._udp_stream.close(null); this._udp.close(); } }; /** * Lan Base Channel * * This class essentially just extends Core.Channel to set TCP socket options * and negotiate TLS encrypted connections. */ var Channel = class Channel extends Core.Channel { get certificate() { return this._connection.get_peer_certificate(); } get type() { return 'tcp'; } _initSocket(connection) { connection.socket.set_keepalive(true); 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 } 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 } return connection; } /** * Handshake Gio.TlsConnection */ _handshake(connection) { return new Promise((resolve, reject) => { connection.validation_flags = Gio.TlsCertificateFlags.EXPIRED; connection.authentication_mode = Gio.TlsAuthenticationMode.REQUIRED; connection.handshake_async( GLib.PRIORITY_DEFAULT, this.cancellable, (connection, res) => { try { resolve(connection.handshake_finish(res)); } catch (e) { reject(e); } } ); }); } async _authenticate(connection) { try { // Standard TLS Handshake await this._handshake(connection); // Get a GSettings object for this deviceId let id = this.identity.body.deviceId; let settings = new Gio.Settings({ settings_schema: gsconnect.gschema.lookup( 'org.gnome.Shell.Extensions.GSConnect.Device', true ), path: `/org/gnome/shell/extensions/gsconnect/device/${id}/` }); let cert_pem = settings.get_string('certificate-pem'); // If we have a certificate for this deviceId, we can verify it if (cert_pem !== '') { let certificate = Gio.TlsCertificate.new_from_pem(cert_pem, -1); let valid = certificate.is_same(connection.peer_certificate); // This is a fraudulent certificate; notify the user if (!valid) { let error = new Error(); error.name = 'AuthenticationError'; error.deviceName = this.identity.body.deviceName; error.deviceHost = connection.base_io_stream.get_remote_address().address.to_string(); this.service.notify_error(error); throw error; } } return connection; } catch (e) { return Promise.reject(e); } } /** * Wrap the connection in Gio.TlsClientConnection and initiate handshake * * @param {Gio.TcpConnection} connection - The unauthenticated connection * @return {Gio.TlsServerConnection} - The authenticated connection */ _clientEncryption(connection) { connection = Gio.TlsClientConnection.new( connection, connection.socket.remote_address ); connection.set_certificate(this.service.certificate); return this._authenticate(connection); } /** * Wrap the connection in Gio.TlsServerConnection and initiate handshake * * @param {Gio.TcpConnection} connection - The unauthenticated connection * @return {Gio.TlsServerConnection} - The authenticated connection */ _serverEncryption(connection) { connection = Gio.TlsServerConnection.new( connection, this.service.certificate ); // We're the server so we trust-on-first-use and verify after let _id = connection.connect('accept-certificate', (connection) => { connection.disconnect(_id); return true; }); return this._authenticate(connection); } }; /** * Lan Transfer Channel */ var Transfer = class Transfer extends Channel { /** * @param {object} params - Transfer parameters * @param {Device.Device} params.device - The device that owns this transfer * @param {Gio.InputStream} params.input_stream - The input stream (read) * @param {Gio.OutputStream} params.output_stream - The output stream (write) * @param {number} params.size - The size of the transfer in bytes */ constructor(params) { super(params); // The device tracks transfers it owns so they can be closed from the // notification action. this.device._transfers.set(this.uuid, this); } get identity() { return this.device._channel.identity; } /** * Override to untrack the transfer UUID */ close() { this.device._transfers.delete(this.uuid); super.close(); } /** * Connect to @port and read from the remote output stream into the local * input stream. * * When finished the channel and local input stream will be closed whether * or not the transfer succeeds. * * @param {number} port - The port the transfer is listening for connection * @return {boolean} - %true on success or %false on fail */ async download(port) { let result = false; try { this._connection = await new Promise((resolve, reject) => { // Connect let client = new Gio.SocketClient({enable_proxy: false}); // Use the address from GSettings with @port let address = Gio.InetSocketAddress.new_from_string( this.device.settings.get_string('tcp-host'), port ); client.connect_async(address, null, (client, res) => { try { resolve(client.connect_finish(res)); } catch (e) { reject(e); } }); }); this._connection = await this._initSocket(this._connection); this._connection = await this._clientEncryption(this._connection); this.input_stream = this._connection.get_input_stream(); // Start the transfer result = await this._transfer(); } catch (e) { logError(e, this.device.name); } finally { this.close(); } return result; } /** * Start listening on the first available port for an incoming connection, * then send @packet with the payload transfer info. When the connection is * accepted write to the remote input stream from the local output stream. * * When finished the channel and local output stream will be closed whether * or not the transfer succeeds. * * @param {Core.Packet} packet - The packet describing the transfer * @return {boolean} - %true on success or %false on fail */ async upload(packet) { let port = 1739; let result = false; try { // Start listening on the first available port between 1739-1764 let listener = new Gio.SocketListener(); while (port <= TCP_MAX_PORT) { try { listener.add_inet_port(port, null); break; } catch (e) { if (port < TCP_MAX_PORT) { port++; continue; } else { throw e; } } } // Await the incoming connection let connection = new Promise((resolve, reject) => { listener.accept_async( this.cancellable, (listener, res, source_object) => { try { resolve(listener.accept_finish(res)[0]); } catch (e) { reject(e); } } ); }); // Notify the device we're ready packet.body.payloadHash = this.checksum; packet.payloadSize = this.size; packet.payloadTransferInfo = {port: port}; this.device.sendPacket(packet); // Accept the connection and configure the channel this._connection = await connection; this._connection = await this._initSocket(this._connection); this._connection = await this._serverEncryption(this._connection); this.output_stream = this._connection.get_output_stream(); // Start the transfer result = await this._transfer(); } catch (e) { logError(e, this.device.name); } finally { this.close(); } return result; } }; gnome-shell-extension-gsconnect-20/src/service/nativeMessagingHost.js000077500000000000000000000151241341554142200262600ustar00rootroot00000000000000#!/usr/bin/env gjs 'use strict'; imports.gi.versions.Gio = '2.0'; imports.gi.versions.GLib = '2.0'; imports.gi.versions.GObject = '2.0'; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const System = imports.system; var 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 Object.values(this._devices); } vfunc_activate() { super.vfunc_activate(); } vfunc_startup() { super.vfunc_startup(); this.hold(); // IO Channels this.stdin = new Gio.DataInputStream({ base_stream: new Gio.UnixInputStream({fd: 0}), byte_order: Gio.DataStreamByteOrder.HOST_ENDIAN }); this.stdout = new Gio.DataOutputStream({ base_stream: new Gio.UnixOutputStream({fd: 1}), byte_order: Gio.DataStreamByteOrder.HOST_ENDIAN }); let source = this.stdin.base_stream.create_source(null); source.set_callback(this.receive.bind(this)); source.attach(null); this._init_async(); } async _init_async(obj, res) { try { this.manager = await new Promise((resolve, reject) => { Gio.DBusObjectManagerClient.new_for_bus( Gio.BusType.SESSION, Gio.DBusObjectManagerClientFlags.DO_NOT_AUTO_START, 'org.gnome.Shell.Extensions.GSConnect', '/org/gnome/Shell/Extensions/GSConnect', null, null, (manager, res) => { try { resolve(Gio.DBusObjectManagerClient.new_for_bus_finish(res)); } catch (e) { reject(e); } } ); }); // Add currently managed devices for (let object of this.manager.get_objects()) { for (let 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: true}); } catch (e) { this.quit(); } } receive() { try { // Read the message let length = this.stdin.read_int32(null); let message = this.stdin.read_bytes(length, null).toArray(); if (message instanceof Uint8Array) { message = imports.byteArray.toString(message); } message = JSON.parse(message); // 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; let 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 true; } catch (e) { this.quit(); } } send(message) { try { let data = JSON.stringify(message); this.stdout.put_int32(data.length, null); this.stdout.put_string(data, null); } catch (e) { this.quit(); } } sendDeviceList() { // Inform the WebExtension we're disconnected from the service if (this.manager && this.manager.name_owner === null) { this.send({type: 'connected', data: false}); return; } let available = []; for (let device of this.devices) { let share = device.actions.get_action_enabled('shareUri'); let telephony = device.actions.get_action_enabled('shareSms'); if (share || telephony) { available.push({ id: device.g_object_path, name: device.name, type: device.type, share: share, telephony: telephony }); } } this.send({type: 'devices', data: available}); } _proxyGetter(name) { try { return this.get_cached_property(name).unpack(); } catch (e) { return null; } } _onInterfaceAdded(manager, object, iface) { Object.defineProperties(iface, { 'name': { get: this._proxyGetter.bind(iface, 'Name'), enumerable: true }, // TODO: phase this out for icon-name 'type': { get: this._proxyGetter.bind(iface, 'Type'), enumerable: true } }); iface.actions = Gio.DBusActionGroup.get( iface.g_connection, iface.g_name, iface.g_object_path ); this._devices[iface.g_object_path] = iface; this.sendDeviceList(); } _onObjectRemoved(manager, object) { delete this._devices[object.g_object_path]; this.sendDeviceList(); } }); // NOTE: must not pass ARGV (new NativeMessagingHost()).run([System.programInvocationName]); gnome-shell-extension-gsconnect-20/src/service/plugins/000077500000000000000000000000001341554142200234135ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/src/service/plugins/base.js000066400000000000000000000151661341554142200246740ustar00rootroot00000000000000'use strict'; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; /** * Base class for plugins */ var Plugin = GObject.registerClass({ GTypeName: 'GSConnectDevicePlugin' }, class Plugin extends GObject.Object { _init(device, name) { super._init(); this._device = device; this._name = name; this._meta = imports.service.plugins[name].Metadata; // Init GSettings this.settings = new Gio.Settings({ settings_schema: gsconnect.gschema.lookup(this._meta.id, false), path: `${gsconnect.settings.path}device/${device.id}/plugin/${name}/` }); // GActions this._gactions = []; if (this._meta.actions) { // Register based on device capabilities, which shouldn't change let deviceHandles = this.device.settings.get_strv('incoming-capabilities'); let deviceProvides = this.device.settings.get_strv('outgoing-capabilities'); let disabled = this.device.settings.get_strv('disabled-actions'); let menu = this.device.settings.get_strv('menu-actions'); for (let name in this._meta.actions) { let meta = this._meta.actions[name]; if (meta.incoming.every(p => deviceProvides.includes(p)) && meta.outgoing.every(p => deviceHandles.includes(p))) { this._registerAction(name, meta, menu, disabled); } } } } get device() { return this._device; } get name() { return this._name; } get service() { return Gio.Application.get_default(); } _activateAction(action, parameter) { try { parameter = parameter ? parameter.full_unpack() : null; if (Array.isArray(parameter)) { this[action.name].apply(this, parameter); } else if (parameter) { this[action.name].call(this, parameter); } else { this[action.name].call(this); } } catch (e) { debug(e); } } _registerAction(name, meta, menu, disabled) { let action = new Gio.SimpleAction({ name: name, parameter_type: meta.parameter_type, state: new GLib.Variant('(ss)', [meta.label, meta.icon_name]) }); // Set the enabled state action.set_enabled(this.device.connected && !disabled.includes(action.name)); // Bind the activation action.connect('activate', this._activateAction.bind(this)); this.device.add_action(action); // Menu let index = menu.indexOf(action.name); if (index > -1) { this.device.menu.add_action(action, index); } this._gactions.push(action); } /** * This is called when a packet is received the plugin is a handler for */ handlePacket(packet) { throw new GObject.NotImplementedError(); } /** * These two methods are optional and called by the device in response to * the connection state changing. */ connected() { let disabled = this.device.settings.get_strv('disabled-actions'); for (let action of this._gactions) { action.set_enabled(!disabled.includes(action.name)); } } disconnected() { for (let action of this._gactions) { action.set_enabled(false); } } /** * Cache JSON parseable properties on this object for persistence. The * filename ~/.cache/gsconnect//.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.__cache_properties = names; // Ensure the device's cache directory exists let cachedir = GLib.build_filenamev([ gsconnect.cachedir, this.device.id ]); GLib.mkdir_with_parents(cachedir, 448); this.__cache_file = Gio.File.new_for_path( GLib.build_filenamev([cachedir, `${this.name}.json`]) ); // Read the cache from disk let cache = await JSON.load(this.__cache_file); 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 cache is done loading */ cacheLoaded() {} /** * Write the plugin's cache to disk */ async __cache_write() { try { if (this.__cache_lock) { this.__cache_queue = true; return; } this.__cache_lock = true; // Build the cache let cache = {}; for (let name of this.__cache_properties) { cache[name] = this[name]; } await JSON.dump(cache, this.__cache_file); } catch (e) { debug(e.message, `${this.device.name}: ${this.name}`); } finally { this.__cache_lock = false; if (this.__cache_queue) { this.__cache_queue = false; this.__cache_write(); } } } /** * Unregister plugin actions, write the cache (if applicable) and destroy * any dangling signal handlers. */ destroy() { this._gactions.map(action => { this.device.menu.remove_action(`device.${action.name}`); this.device.remove_action(action.name); }); // Write the cache to disk synchronously if (this.__cache_file && !this.__cache_lock) { try { // Build the cache let cache = {}; for (let name of this.__cache_properties) { cache[name] = this[name]; } JSON.dump(cache, this.__cache_file, true); } catch (e) { debug(e.message, `${this.device.name}: ${this.name}`); } } // Try to avoid any cyclic references from signal handlers GObject.signal_handlers_destroy(this); GObject.signal_handlers_destroy(this.settings); } }); gnome-shell-extension-gsconnect-20/src/service/plugins/battery.js000066400000000000000000000226261341554142200254330ustar00rootroot00000000000000'use strict'; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const DBus = imports.service.components.dbus; const PluginsBase = imports.service.plugins.base; var Metadata = { label: _('Battery'), 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 */ var Plugin = GObject.registerClass({ GTypeName: 'GSConnectBatteryPlugin' }, class Plugin extends PluginsBase.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._upowerId = 0; 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'; } icon += this.charging ? '-charging-symbolic' : '-symbolic'; return icon; } 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] ); } cacheClear() { this._chargeState = [54, 0, -1]; this._dischargeState = [864, 0, -1]; this._thresholdLevel = 25; this.__cache_write(); } cacheLoaded() { this._estimateTime(); this.connected(); } _onSendStatisticsChanged() { if (this.settings.get_boolean('send-statistics')) { this._monitorState(); } else { this._unmonitorState(); } } handlePacket(packet) { switch (packet.type) { case 'kdeconnect.battery': this._receiveState(packet); break; case 'kdeconnect.battery.request': this._sendState(); break; } } connected() { super.connected(); this._requestState(); this._sendState(); } /** * Notify that the remote device considers the battery level low */ _notifyState() { let buttons = []; // Offer the option to locate the device, if available if (this.device.get_action_enabled('ring')) { buttons = [{ label: _('Ring'), action: 'ring', parameter: null }]; } this.device.showNotification({ id: 'battery|threshold', // 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: new Gio.ThemedIcon({name: 'battery-caution-symbolic'}), buttons: buttons }); // Save the threshold level this._thresholdLevel = this.level; } /** * Handle a remote battery update. * * @param {kdeconnect.battery} 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 (this._level > this._thresholdLevel) { this.device.hideNotification('battery|threshold'); } } // Device considers the level low if (packet.body.thresholdEvent > 0) { this._notifyState(); } this._updateEstimate(); this.__state.state = this.state; } /** * Request the remote battery's current charge/state */ _requestState() { this.device.sendPacket({ id: 0, type: 'kdeconnect.battery.request', body: {request: true} }); } /** * Report the local battery's current charge/state */ _sendState() { if (this._upowerId === 0) { return; } this.device.sendPacket({ type: 'kdeconnect.battery', body: { currentCharge: this.service.upower.level, isCharging: this.service.upower.charging, thresholdEvent: this.service.upower.threshold } }); } /** * UPower monitoring methods */ _monitorState() { try { switch (true) { // upower failed, already monitoring, no battery or no support case (!this.service.upower): case (this._upowerId > 0): case (this.service.type !== 'laptop'): case (!this.device.get_incoming_supported('battery')): return; } this._upowerId = this.service.upower.connect( 'changed', this._sendState.bind(this) ); this._sendState(); } catch (e) { logError(e, this.device.name); this._unmonitorState(); } } _unmonitorState() { if (this._upowerId > 0) { this.service.upower.disconnect(this._upowerId); this._upowerId = 0; } } /** * Recalculate the (dis)charge rate and update the estimated time remaining * See also: https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/os/BatteryStats.java#1036 */ _updateEstimate() { let new_time = Math.floor(Date.now() / 1000); let new_level = this.level; // Read the state; rate has a default, time and level default to current let [rate, time, level] = this.charging ? this._chargeState : this._dischargeState; time = (Number.isFinite(time) && time > 0) ? time : new_time; level = (Number.isFinite(level) && level > -1) ? level : new_level; if (!Number.isFinite(rate) || rate < 1) { rate = this.charging ? 54 : 864; } // Derive rate from time/level diffs (rate = seconds/percent) let ldiff = this.charging ? new_level - level : level - new_level; let tdiff = new_time - time; let new_rate = tdiff / ldiff; // Update the rate if it seems valid. Use a weighted average in favour // of the new rate to account for possible missed level changes if (new_rate && Number.isFinite(new_rate)) { rate = Math.floor((rate * 0.4) + (new_rate * 0.6)); } // Save the state if (this.charging) { this._chargeState = [rate, new_time, new_level]; } else { this._dischargeState = [rate, new_time, new_level]; } // Notify of the change if (rate && this.charging) { this._time = Math.floor(rate * (100 - new_level)); } else if (rate && !this.charging) { this._time = Math.floor(rate * new_level); } } _estimateTime() { // elision (rate, time, level) let [rate,, level] = this.charging ? this._chargeState : this._dischargeState; level = (level > -1) ? level : this.level; if (!Number.isFinite(rate) || rate < 1) { rate = this.charging ? 864 : 90; } 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; } destroy() { this.settings.disconnect(this._sendStatisticsId); this._unmonitorState(); this.device.remove_action('battery'); super.destroy(); } }); gnome-shell-extension-gsconnect-20/src/service/plugins/clipboard.js000066400000000000000000000063011341554142200257100ustar00rootroot00000000000000'use strict'; const Gdk = imports.gi.Gdk; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const PluginsBase = imports.service.plugins.base; var Metadata = { label: _('Clipboard'), id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.Clipboard', incomingCapabilities: ['kdeconnect.clipboard'], outgoingCapabilities: ['kdeconnect.clipboard'], 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 */ var Plugin = GObject.registerClass({ GTypeName: 'GSConnectClipboardPlugin', }, class Plugin extends PluginsBase.Plugin { _init(device) { super._init(device, 'clipboard'); try { let display = Gdk.Display.get_default(); this._clipboard = Gtk.Clipboard.get_default(display); } catch (e) { this.destroy(); throw e; } // Buffer content to allow selective sync this._localBuffer = ''; this._remoteBuffer = ''; // Watch local clipboard for changes this._ownerChangeId = this._clipboard.connect( 'owner-change', this._onLocalClipboardChanged.bind(this) ); } handlePacket(packet) { if (packet.body.hasOwnProperty('content')) { this._onRemoteClipboardChanged(packet.body.content); } } /** * Store the updated clipboard content and forward it if enabled */ _onLocalClipboardChanged(clipboard, event) { clipboard.request_text((clipboard, text) => { this._localBuffer = text; if (this.settings.get_boolean('send-content')) { this.clipboardPush(); } }); } /** * Store the updated 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() { if (this._remoteBuffer !== this._localBuffer) { this._remoteBuffer = this._localBuffer; 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._clipboard.set_text(this._remoteBuffer, -1); } } destroy() { this._clipboard.disconnect(this._ownerChangeId); super.destroy(); } }); gnome-shell-extension-gsconnect-20/src/service/plugins/contacts.js000066400000000000000000000243501341554142200255730ustar00rootroot00000000000000'use strict'; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const PluginsBase = imports.service.plugins.base; const Contacts = imports.service.components.contacts; var Metadata = { label: _('Contacts'), 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 FIELD_BASIC = /^([^:;]+):(.+)$/; const FIELD_TYPED = /^([^:;]+);([^:]+):(.+)$/; const FIELD_TYPED_KEY = /item\d{1,2}\./; const FIELD_TYPED_META = /([a-z]+)=(.*)/i; /** * Contacts Plugin * https://github.com/KDE/kdeconnect-kde/tree/master/plugins/contacts */ var Plugin = GObject.registerClass({ GTypeName: 'GSConnectContactsPlugin' }, class Plugin extends PluginsBase.Plugin { _init(device) { super._init(device, 'contacts'); this._store = new Contacts.Store(device.id); // 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') ); // Prepare the store this._store.prepare(); } connected() { super.connected(); this.requestUids(); } cacheClear() { this._store.clear(); } handlePacket(packet) { if (packet.type === 'kdeconnect.contacts.response_uids_timestamps') { this._handleUids(packet); } else if (packet.type === 'kdeconnect.contacts.response_vcards') { this._handleVCards(packet); } } _handleUids(packet) { try { // Delete any contacts that were removed on the device let contacts = this._store.contacts; let remote_uids = packet.body.uids; let removed = false; delete packet.body.uids; for (let i = 0, len = contacts.length; i < len; i++) { let contact = contacts[i]; if (!remote_uids.includes(contact.id)) { this._store.remove(contact.id, false); removed = true; } } if (removed) this._store.__cache_write(); // Build a list of new or updated contacts let uids = []; for (let [uid, timestamp] of Object.entries(packet.body)) { let 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); } } catch (e) { logError(e); } } /** * Decode a string encoded as "QUOTED-PRINTABLE" and return a regular string * * See: https://github.com/mathiasbynens/quoted-printable/blob/master/src/quoted-printable.js * * @param {string} input - The QUOTED-PRINTABLE string * @return {string} - The decoded string */ decode_quoted_printable(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) => { let codePoint = parseInt($1, 16); return String.fromCharCode(codePoint); }); } /** * Decode a string encoded as "UTF-8" and return a regular string * * See: https://github.com/kvz/locutus/blob/master/src/php/xml/utf8_decode.js * * @param {string} input - The UTF-8 string * @return {string} - The decoded string */ decode_utf8(input) { try { let 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 = (c1 & 0x7F); seqlen = 1; } else if (c1 <= 0xDF) { c1 = (c1 & 0x1F); seqlen = 2; } else if (c1 <= 0xEF) { c1 = (c1 & 0x0F); seqlen = 3; } else { c1 = (c1 & 0x07); seqlen = 4; } for (let ai = 1; ai < seqlen; ++ai) { c1 = ((c1 << 0x06) | (input.charCodeAt(ai + i) & 0x3F)); } if (seqlen === 4) { c1 -= 0x10000; output.push(String.fromCharCode(0xD800 | ((c1 >> 10) & 0x3FF))); output.push(String.fromCharCode(0xDC00 | (c1 & 0x3FF))); } else { output.push(String.fromCharCode(c1)); } i += seqlen; } return output.join(''); // Fallback to old unfaithful } catch (e) { try { return decodeURIComponent(escape(input)); // Say "chowdah" frenchie! } catch (e) { warning(`Failed to decode UTF-8 VCard field "${input}"`); return input; } } } /** * Parse a VCard v2.1 and return a dictionary of data * * See: http://jsfiddle.net/ARTsinn/P2t2P/ * * @param {string} vcard_data - The raw VCard data */ parseVCard21(vcard_data) { // vcard skeleton let vcard = { fn: _('Unknown Contact'), tel: [] }; // Remove line folding and split let lines = vcard_data.replace(/\n /g, '').split('\n'); for (let i = 0, len = lines.length; i < len; i++) { let line = lines[i]; let results, key, type, value; // Empty line if (!line) continue; // Basic Fields (fn, x-kdeconnect-timestamp, etc) if ((results = line.match(FIELD_BASIC))) { [results, key, value] = results; vcard[key.toLowerCase()] = value; continue; } // Typed Fields (tel, adr, etc) if ((results = line.match(FIELD_TYPED))) { [results, key, type, value] = results; key = key.replace(FIELD_TYPED_KEY, '').toLowerCase(); value = value.split(';'); type = type.split(';'); // Type(s) let meta = {}; for (let i = 0, len = type.length; i < len; i++) { let res = type[i].match(FIELD_TYPED_META); if (res) { meta[res[1]] = res[2]; } else { meta['type' + (i === 0 ? '' : i)] = type[i].toLowerCase(); } } // Value(s) if (!vcard[key]) vcard[key] = []; // Decode QUOTABLE-PRINTABLE if (meta.ENCODING && meta.ENCODING === 'QUOTED-PRINTABLE') { delete meta.ENCODING; value = value.map(v => this.decode_quoted_printable(v)); } // Decode UTF-8 if (meta.CHARSET && meta.CHARSET === 'UTF-8') { delete meta.CHARSET; value = value.map(v => this.decode_utf8(v)); } // Special case for FN (full name) if (key === 'fn') { vcard[key] = value[0]; } else { vcard[key].push({meta: meta, value: value}); } } } return vcard; } async parseContact(uid, vcard_data) { try { let vcard = this.parseVCard21(vcard_data); let 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) { let data = GLib.base64_decode(vcard.photo[0].value[0]); contact.avatar = await this._store.storeAvatar(data); } return contact; } catch (e) { warning(e, `Failed to parse VCard contact "${uid}"`); return undefined; } } async _handleVCards(packet) { try { // We don't use this delete packet.body.uids; // Parse each vCard and add the contact for (let [uid, vcard] of Object.entries(packet.body)) { let contact = await this.parseContact(uid, vcard); if (contact) { this._store.add(contact); } } } catch (e) { logError(e); } } requestUids() { this.device.sendPacket({ type: 'kdeconnect.contacts.request_all_uids_timestamps' }); } requestVCards(uids) { this.device.sendPacket({ type: 'kdeconnect.contacts.request_vcards_by_uid', body: { uids: uids } }); } destroy() { this.settings.disconnect(this._contactsStoreReadyId); this.settings.disconnect(this._contactsSourceChangedId); super.destroy(); } }); gnome-shell-extension-gsconnect-20/src/service/plugins/findmyphone.js000066400000000000000000000152031341554142200262720ustar00rootroot00000000000000'use strict'; const Gdk = imports.gi.Gdk; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const PluginsBase = imports.service.plugins.base; var Metadata = { label: _('Find My Phone'), id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.FindMyPhone', incomingCapabilities: ['kdeconnect.findmyphone.request'], outgoingCapabilities: ['kdeconnect.findmyphone.request'], actions: { ring: { label: _('Ring'), icon_name: 'find-location-symbolic', parameter_type: null, incoming: [], outgoing: ['kdeconnect.findmyphone.request'] } } }; /** * FindMyPhone Plugin * https://github.com/KDE/kdeconnect-kde/tree/master/plugins/findmyphone * * TODO: cancel incoming requests on disconnect? */ var Plugin = GObject.registerClass({ GTypeName: 'GSConnectFindMyPhonePlugin', }, class Plugin extends PluginsBase.Plugin { _init(device) { super._init(device, 'findmyphone'); } handlePacket(packet) { if (packet.type === 'kdeconnect.findmyphone.request') { this._handleRequest(); } } /** * Handle an incoming location request. */ _handleRequest() { try { // If this is a second request, stop announcing and return if (this._dialog) { this._dialog.response(Gtk.ResponseType.DELETE_EVENT); return; } this._dialog = new Dialog(this.device.name); this._dialog.connect('response', () => this._dialog = null); } catch (e) { this._cancelRequest(); logError(e, this.device.name); } } _cancelRequest() { if (this._dialog) { this._dialog.response(Gtk.ResponseType.DELETE_EVENT); } } /** * Request the remote device announce it's location */ ring() { this.device.sendPacket({ type: 'kdeconnect.findmyphone.request', body: {} }); } destroy() { this._cancelRequest(); super.destroy(); } }); /** * Return the backend to be used for playing sound effects * * @return {string|boolean} - 'gsound', 'libcanberra' or %false */ function get_backend() { if (window._SFX_BACKEND) { return _SFX_BACKEND; } // Service-wide GSound.Context singleton try { window._GSOUND_CONTEXT = new imports.gi.GSound.Context(); window._GSOUND_CONTEXT.init(null); window._SFX_BACKEND = 'gsound'; // Try falling back to libcanberra } catch (e) { if (GLib.find_program_in_path('canberra-gtk-play') !== null) { window._SFX_BACKEND = 'libcanberra'; } } return _SFX_BACKEND; } /** * 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' }, class Dialog extends Gtk.MessageDialog { _init(name) { super._init({ buttons: Gtk.ButtonsType.CLOSE, image: new Gtk.Image({ icon_name: 'find-location-symbolic', pixel_size: 128 }), urgency_hint: true, window_position: Gtk.WindowPosition.CENTER_ALWAYS }); this.set_keep_above(true); this.message_area.destroy(); // Ensure the volume is sufficient let mixer = Gio.Application.get_default().pulseaudio; if (mixer) { this._stream = mixer.output; this._previousVolume = this._stream.volume; this._previousMuted = this._stream.muted; this._stream.volume = 0.85; this._stream.muted = false; } // Ensure audible-bell is enabled for fallback this._previousBell = WM_SETTINGS.get_boolean('audible-bell'); WM_SETTINGS.set_boolean('audible-bell', true); // Show the dialog and start the alarm this.show_all(); this._cancellable = new Gio.Cancellable(); this.bell(); } vfunc_response(response_id) { // Stop the alarm this._cancellable.cancel(); // Restore the mixer level if (this._stream) { this._stream.volume = this._previousVolume; this._stream.muted = this._previousMuted; } // Restore the audible-bell WM_SETTINGS.set_boolean('audible-bell', this._previousBell); this.destroy(); } bell() { let proc; switch (get_backend()) { case 'gsound': _GSOUND_CONTEXT.play_full( {'event.id': 'phone-incoming-call'}, this._cancellable, (source, res) => { try { source.play_full_finish(res); this.bell(); } catch (e) { } } ); break; case 'libcanberra': proc = new Gio.Subprocess({ argv: ['canberra-gtk-play', '-i', 'phone-incoming-call'], flags: Gio.SubprocessFlags.NONE }); proc.init(null); proc.wait_check_async(this._cancellable, (proc, res) => { try { proc.wait_check_finish(res); this.bell(); } catch (e) { } }); break; default: this._display = Gdk.Display.get_default(); this._fallback(); GLib.timeout_add( GLib.PRIORITY_DEFAULT, 1500, () => this._fallback() ); } } /** * A fallback for playing an alert using gdk_display_bell() when neither * GSound nor canberra-gtk-play are available. */ _fallback() { let count = 0; GLib.timeout_add(GLib.PRIORITY_DEFAULT, 200, () => { try { if (count++ < 4 && !this._cancellable.is_cancelled()) { this._display.beep(); return GLib.SOURCE_CONTINUE; } return GLib.SOURCE_REMOVE; } catch (e) { return GLib.SOURCE_REMOVE; } }); return !this._cancellable.is_cancelled(); } }); gnome-shell-extension-gsconnect-20/src/service/plugins/mousepad.js000066400000000000000000000557061341554142200256030ustar00rootroot00000000000000'use strict'; const Atspi = imports.gi.Atspi; const Gdk = imports.gi.Gdk; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const PluginsBase = imports.service.plugins.base; var Metadata = { label: _('Mousepad'), 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: _('Keyboard'), icon_name: 'input-keyboard-symbolic', parameter_type: null, incoming: ['kdeconnect.mousepad.echo', 'kdeconnect.mousepad.keyboardstate'], outgoing: ['kdeconnect.mousepad.request'] } } }; const _ASCII = /[\x20-\x7E]/; /** * 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] ]); /** * Mousepad Plugin * https://github.com/KDE/kdeconnect-kde/tree/master/plugins/mousepad * * TODO: support outgoing mouse events? */ var Plugin = GObject.registerClass({ GTypeName: 'GSConnectMousepadPlugin', Properties: { 'state': GObject.ParamSpec.boolean( 'state', 'State', 'Remote keyboard state', GObject.ParamFlags.READABLE, false ), 'share-control': GObject.ParamSpec.boolean( 'share-control', 'Share Control', 'Share control of mouse & keyboard', GObject.ParamFlags.READWRITE, false ) } }, class Plugin extends PluginsBase.Plugin { _init(device) { super._init(device, 'mousepad'); // 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) { Atspi.exit(); 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; } this.settings.bind( 'share-control', this, 'share-control', Gio.SettingsBindFlags.GET ); this._state = false; this._stateId = 0; } connected() { super.connected(); // Recheck for Caribou if (!this._virtual_keyboard) { try { let Caribou = imports.gi.Caribou; this._virtual_keyboard = Caribou.DisplayAdapter.get_default(); } catch (e) { this._virtual_keyboard = false; } } this.sendState(); } disconnected() { super.disconnected(); this._state = false; this._stateId = 0; this.notify('state'); } get state() { return (this._state); } get virtual_keyboard() { return this._virtual_keyboard; } handlePacket(packet) { switch (packet.type) { case 'kdeconnect.mousepad.request': if (this.share_control) { this._handleInput(packet.body); } break; case 'kdeconnect.mousepad.echo': this._handleEcho(packet.body); break; case 'kdeconnect.mousepad.keyboardstate': this._handleState(packet); break; } } _handleInput(input) { let mask = 0; // 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'): if (input.dy < 0) { this.clickPointer(5); } else if (input.dy > 0) { this.clickPointer(4); } break; case (input.hasOwnProperty('dx') && input.hasOwnProperty('dy')): this.movePointer(input.dx, input.dy); break; case (input.hasOwnProperty('key') || input.hasOwnProperty('specialKey')): // We need to decide if we have modifiers to deal with if (input.alt) mask |= Gdk.ModifierType.MOD1_MASK; if (input.ctrl) mask |= Gdk.ModifierType.CONTROL_MASK; if (input.shift) mask |= Gdk.ModifierType.SHIFT_MASK; if (input.super) mask |= Gdk.ModifierType.MOD4_MASK; // Special key without modifiers (eg. non-printable ASCII) if (input.specialKey && KeyMap.has(input.specialKey) && !mask) { this.pressSpecialKey(input); // Regular key without modifiers (printable ASCII) // NOTE: Passing AT-SPI non-ASCII keyvals may crash Xorg } else if (input.key && _ASCII.test(input.key) && !mask) { this.pressKey(input); // Caribou/XTest is required; modifiers and/or unicode } else if (this.virtual_keyboard) { this.pressKeySym(input, mask); // Caribou not available or key out of range } else { warning(_('Additional Software Required') + ': libcaribou'); } break; case input.hasOwnProperty('singleclick'): this.clickPointer(1); break; case input.hasOwnProperty('doubleclick'): this.doubleclickPointer(1); break; case input.hasOwnProperty('middleclick'): this.clickPointer(2); break; case input.hasOwnProperty('rightclick'): this.clickPointer(3); break; case input.hasOwnProperty('singlehold'): this.pressPointer(1); break; // This is not used, hold is released with a regular click instead case input.hasOwnProperty('singlerelease'): this.releasePointer(1); break; } } /** * Pointer events */ clickPointer(button) { try { let [, x, y] = this._pointer.get_position(); let monitor = this._display.get_monitor_at_point(x, y); let scale = monitor.get_scale_factor(); Atspi.generate_mouse_event(scale * x, scale * y, `b${button}c`); } catch (e) { logError(e, this.device.name); } } doubleclickPointer(button) { try { let [, x, y] = this._pointer.get_position(); let monitor = this._display.get_monitor_at_point(x, y); let scale = monitor.get_scale_factor(); Atspi.generate_mouse_event(scale * x, scale * y, `b${button}d`); } catch (e) { logError(e, this.device.name); } } movePointer(dx, dy) { try { let [, x, y] = this._pointer.get_position(); let monitor = this._display.get_monitor_at_point(x, y); let scale = monitor.get_scale_factor(); Atspi.generate_mouse_event(scale * dx, scale * dy, 'rel'); } catch (e) { logError(e, this.device.name); } } pressPointer(button) { try { let [, x, y] = this._pointer.get_position(); let monitor = this._display.get_monitor_at_point(x, y); let scale = monitor.get_scale_factor(); Atspi.generate_mouse_event(scale * x, scale * y, `b${button}p`); } catch (e) { logError(e, this.device.name); } } releasePointer(button) { try { let [, x, y] = this._pointer.get_position(); let monitor = this._display.get_monitor_at_point(x, y); let scale = monitor.get_scale_factor(); Atspi.generate_mouse_event(scale * x, scale * y, `b${button}r`); } catch (e) { logError(e, this.device.name); } } /** * Simulate a keypress in the printable ASCII range (x20-x7E) * * @param {object} input - 'body' of a 'kdeconnect.mousepad.request' packet */ pressKey(input) { try { debug(`key: ${input.key}`); Atspi.generate_keyboard_event( 0, input.key, Atspi.KeySynthType.STRING ); this.sendEcho(input); } catch (e) { logError(e, this.device.name); } } /** * Simulate a special key from KeyMap (F1, Home, Esc, etc) * * @param {object} input - 'body' of a 'kdeconnect.mousepad.request' packet */ pressSpecialKey(input) { try { debug(`specialKey: ${KeyMap.get(input.specialKey)}`); Atspi.generate_keyboard_event( KeyMap.get(input.specialKey), null, Atspi.KeySynthType.PRESSRELEASE | Atspi.KeySynthType.SYM ); this.sendEcho(input); } catch (e) { logError(e, this.device.name); } } /** * Simulate a key press using libcaribou * * @param {object} input - 'body' of a 'kdeconnect.mousepad.request' packet * @param {number} mask - Modifier mask for keypress */ pressKeySym(input, mask) { try { debug('simulating keypress with libcaribou'); // Transform key to keyval let keyval; // Regular key (Unicode) // NOTE: \u0000 sometimes sent in advance of a specialKey packet if (input.key && input.key !== '\u0000') { keyval = Gdk.unicode_to_keyval(input.key.codePointAt(0)); // Special key (eg. non-printable ASCII) } else if (input.specialKey && KeyMap.has(input.specialKey)) { keyval = KeyMap.get(input.specialKey); // Invalid or unknown key } else { warning(`invalid or unknown key "${input.key || input.specialKey}"`); return; } debug(`keyval: ${keyval}, mask: ${mask}`); // Ensure this a valid keyval if (Gdk.keyval_to_unicode(keyval) !== 0) { this.virtual_keyboard.mod_lock(mask); this.virtual_keyboard.keyval_press(keyval); this.virtual_keyboard.keyval_release(keyval); this.virtual_keyboard.mod_unlock(mask); this.sendEcho(input); } } catch (e) { logError(e, this.device.name); } } /** * Send an echo/ACK of @input, if requested * * @param {object} input - 'body' of a 'kdeconnect.mousepad.request' packet */ sendEcho(input) { if (input.sendAck) { delete input.sendAck; input.isAck = true; this.device.sendPacket({ type: 'kdeconnect.mousepad.echo', body: input }); } } _handleEcho(input) { if (!this._dialog || !this._dialog.visible) { return; } if (input.alt || input.ctrl || input.super) { return; } if (input.key) { this._dialog.text.buffer.text += input.key; } else if (KeyMap.get(input.specialKey) === Gdk.KEY_BackSpace) { this._dialog.text.emit('backspace'); } } _handleState(packet) { // FIXME: ensure we don't get packets out of order if (packet.id > this._stateId) { this._state = packet.body.state; this._stateId = packet.id; this.notify('state'); } } /** * Send the local keyboard state * * @param {boolean} state - Whether we're ready to accept input */ sendState() { this.device.sendPacket({ type: 'kdeconnect.mousepad.keyboardstate', body: { state: this.share_control } }); } /** * Open the Keyboard Input dialog */ keyboard() { if (!this._dialog) { this._dialog = new KeyboardInputDialog({ device: this.device, plugin: this }); } this._dialog.present(); } }); /** * 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); var KeyboardInputDialog = GObject.registerClass({ GTypeName: 'GSConnectMousepadKeyboardInputDialog', Properties: { 'device': GObject.ParamSpec.object( 'device', 'Device', 'The device associated with this window', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, GObject.Object ), 'plugin': GObject.ParamSpec.object( 'plugin', 'Plugin', 'The mousepad plugin associated with this window', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, GObject.Object ) } }, class KeyboardInputDialog extends Gtk.Dialog { _init(params) { super._init(Object.assign({ use_header_bar: true, default_width: 480, window_position: Gtk.WindowPosition.CENTER }, params)); let headerbar = this.get_titlebar(); headerbar.title = _('Keyboard'); headerbar.subtitle = this.device.name; // Main Box let content = this.get_content_area(); content.border_width = 0; // Infobar this.infobar = new Gtk.Revealer(); content.add(this.infobar); let bar = new Gtk.InfoBar({message_type: Gtk.MessageType.WARNING}); this.infobar.add(bar); let infoicon = new Gtk.Image({icon_name: 'dialog-warning-symbolic'}); bar.get_content_area().add(infoicon); let infolabel = new Gtk.Label({label: _('Keyboard not ready')}); bar.get_content_area().add(infolabel); // Content let layout = new Gtk.Grid({ column_spacing: 6, margin: 6 }); content.add(layout); // Modifier Buttons this.shift_label = new Gtk.ShortcutLabel({ accelerator: Gtk.accelerator_name(0, Gdk.ModifierType.SHIFT_MASK), halign: Gtk.Align.END, valign: Gtk.Align.START, sensitive: false }); layout.attach(this.shift_label, 0, 0, 1, 1); this.ctrl_label = new Gtk.ShortcutLabel({ accelerator: Gtk.accelerator_name(0, Gdk.ModifierType.CONTROL_MASK), halign: Gtk.Align.END, valign: Gtk.Align.START, sensitive: false }); layout.attach(this.ctrl_label, 0, 1, 1, 1); this.alt_label = new Gtk.ShortcutLabel({ accelerator: Gtk.accelerator_name(0, Gdk.ModifierType.MOD1_MASK), halign: Gtk.Align.END, valign: Gtk.Align.START, sensitive: false }); layout.attach(this.alt_label, 0, 2, 1, 1); this.super_label = new Gtk.ShortcutLabel({ accelerator: Gtk.accelerator_name(0, Gdk.ModifierType.SUPER_MASK), halign: Gtk.Align.END, valign: Gtk.Align.START, sensitive: false }); layout.attach(this.super_label, 0, 3, 1, 1); // Text Input let scroll = new Gtk.ScrolledWindow({ hscrollbar_policy: Gtk.PolicyType.NEVER, shadow_type: Gtk.ShadowType.IN }); layout.attach(scroll, 1, 0, 1, 4); this.text = new Gtk.TextView({ border_width: 6, hexpand: true, vexpand: true, visible: true }); scroll.add(this.text); this.infobar.connect('notify::reveal-child', this._onState.bind(this)); this.plugin.bind_property('state', this.infobar, 'reveal-child', 6); this.show_all(); } vfunc_delete_event(event) { this._ungrab(); return this.hide_on_delete(); } vfunc_key_release_event(event) { if (!this.plugin.state) { return true; } 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); return super.vfunc_key_release_event(event); } vfunc_key_press_event(event) { if (!this.plugin.state) { return true; } 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) { debug(`keyval: ${event.keyval}, mask: ${realMask}`); let 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 { let 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_window_state_event(event) { if (this.plugin.state && !!(event.new_window_state & Gdk.WindowState.FOCUSED)) { this._grab(); } else { this._ungrab(); } return super.vfunc_window_state_event(event); } _onState(widget) { if (this.plugin.state && this.is_active) { this._grab(); } else { this._ungrab(); } } _grab() { if (!this.visible || this._device) return; debug('acquiring grab'); let seat = Gdk.Display.get_default().get_default_seat(); let status = seat.grab( this.get_window(), Gdk.SeatCapabilities.KEYBOARD, false, null, null, null ); if (status !== Gdk.GrabStatus.SUCCESS) { warning('Grabbing keyboard failed'); return; } this._device = seat.get_keyboard(); this.grab_add(); this.text.has_focus = true; } _ungrab() { if (this._device) { debug('releasing grab'); this._device.get_seat().ungrab(); this._device = null; this.grab_remove(); } this.text.buffer.text = ''; } }); gnome-shell-extension-gsconnect-20/src/service/plugins/mpris.js000066400000000000000000000212201341554142200251000ustar00rootroot00000000000000'use strict'; const Gio = imports.gi.Gio; const GObject = imports.gi.GObject; const PluginsBase = imports.service.plugins.base; var Metadata = { label: _('MPRIS'), id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.MPRIS', incomingCapabilities: ['kdeconnect.mpris.request'], outgoingCapabilities: ['kdeconnect.mpris'], 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 * https://github.com/JasonLG1979/gnome-shell-extensions-mediaplayer/wiki/Known-Player-Bugs * * TODO: It's probably possible to mirror a remote MPRIS2 player on local DBus * https://github.com/KDE/kdeconnect-kde/commit/9e0d4874c072646f1018ad413d59d1f43e590777 */ var Plugin = GObject.registerClass({ GTypeName: 'GSConnectMPRISPlugin', }, class Plugin extends PluginsBase.Plugin { _init(device) { super._init(device, 'mpris'); try { this._notifyPlayersId = this.service.mpris.connect( 'notify::players', this._sendPlayerList.bind(this) ); this._playerChangedId = this.service.mpris.connect( 'player-changed', this._onPlayerChanged.bind(this) ); this._playerSeekedId = this.service.mpris.connect( 'player-seeked', this._onPlayerSeeked.bind(this) ); } catch (e) { this.destroy(); throw new Error('mpris-error'); } } handlePacket(packet) { switch (true) { case packet.body.requestPlayerList: case !this.service.mpris.players.has(packet.body.player): this._sendPlayerList(); break; case packet.body.hasOwnProperty('albumArtUrl'): this._sendAlbumArt(packet); break; default: this._handleCommand(packet); } } connected() { super.connected(); this._sendPlayerList(); } /** * Handle an incoming player command or information request * * @param {kdeconnect.mpris.request} - A command for a specific player */ async _handleCommand(packet) { if (this._updating || !this.settings.get_boolean('share-players')) { return; } try { this._updating = true; let player = this.service.mpris.players.get(packet.body.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: logError(new Error(`unknown action: ${packet.body.action}`)); } } // Player Properties 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')) { let offset = (packet.body.SetPosition * 1000) - player.Position; await player.Seek(offset); } // Information Request let hasResponse = false; let response = { type: 'kdeconnect.mpris', body: {} }; if (packet.body.hasOwnProperty('requestNowPlaying')) { hasResponse = true; 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 }; Object.assign(response.body, this._getPlayerMetadata(player)); } if (packet.body.hasOwnProperty('requestVolume')) { hasResponse = true; response.body.volume = player.Volume * 100; } if (hasResponse) { response.body.player = packet.body.player; this.device.sendPacket(response); } } catch (e) { logError(e); } finally { this._updating = false; } } /** * Get the track metadata for a player * * @param {Gio.DBusProxy} player - The player to get track info for * @return {Object} - An object of track data in MPRIS packet body format */ _getPlayerMetadata(player) { let metadata = {}; try { if (player.Metadata !== null) { let nowPlaying = player.Metadata['xesam:title']; if (player.Metadata.hasOwnProperty('xesam:artist')) { nowPlaying = `${player.Metadata['xesam:artist']} - ${nowPlaying}`; } metadata.nowPlaying = nowPlaying; if (player.Metadata.hasOwnProperty('mpris:artUrl')) { metadata.albumArtUrl = player.Metadata['mpris:artUrl']; } if (player.Metadata.hasOwnProperty('mpris:length')) { metadata.length = Math.floor(player.Metadata['mpris:length'] / 1000); } } } catch (e) { logError(e); } return metadata; } _onPlayerChanged(mpris, player) { if (!this.settings.get_boolean('share-players')) { return; } this._handleCommand({ body: { player: player.Identity, requestNowPlaying: true, requestVolume: true } }); } _onPlayerSeeked(mpris, player) { this.device.sendPacket({ type: 'kdeconnect.mpris', body: { player: player.Identity, pos: Math.floor(player.Position / 1000) } }); } async _sendAlbumArt(packet) { try { // Reject concurrent requests for album art if (this._transferring) { return; } let player = this.service.mpris.players.get(packet.body.player); if (player.Metadata === null) { return; } // Ensure the requested albumArtUrl matches the current mpris:artUrl if (packet.body.albumArtUrl !== player.Metadata['mpris:artUrl']) { return; } // Start the transfer process this._transferring = true; let file = Gio.File.new_for_uri(packet.body.albumArtUrl); let transfer = this.device.createTransfer({ input_stream: file.read(null), size: file.query_info('standard::size', 0, null).get_size() }); await transfer.upload({ id: 0, type: 'kdeconnect.mpris', body: { transferringAlbumArt: true, player: packet.body.player, albumArtUrl: packet.body.albumArtUrl } }); } catch (e) { warning(e, `${this.device.name}: transferring album art`); } finally { this._transferring = false; } } /** * 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.service.mpris.identities; } this.device.sendPacket({ id: 0, type: 'kdeconnect.mpris', body: { playerList: playerList, supportAlbumArtPayload: (this.device.connection_type === 'tcp') } }); } destroy() { try { this.service.mpris.disconnect(this._notifyPlayersId); this.service.mpris.disconnect(this._playerChangedId); this.service.mpris.disconnect(this._playerSeekedId); } catch (e) { } super.destroy(); } }); gnome-shell-extension-gsconnect-20/src/service/plugins/notification.js000066400000000000000000000467151341554142200264540ustar00rootroot00000000000000'use strict'; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const PluginsBase = imports.service.plugins.base; const NotificationUI = imports.service.ui.notification; var Metadata = { label: _('Notifications'), id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.Notification', incomingCapabilities: [ 'kdeconnect.notification', 'kdeconnect.notification.request' ], outgoingCapabilities: [ 'kdeconnect.notification', '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'] } } }; // 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 ]; /** * Notification Plugin * https://github.com/KDE/kdeconnect-kde/tree/master/plugins/notifications * https://github.com/KDE/kdeconnect-kde/tree/master/plugins/sendnotifications */ var Plugin = GObject.registerClass({ GTypeName: 'GSConnectNotificationPlugin' }, class Plugin extends PluginsBase.Plugin { _init(device) { super._init(device, 'notification'); this._sms = {}; } handlePacket(packet) { switch (packet.type) { case 'kdeconnect.notification': return this._handleNotification(packet); case 'kdeconnect.notification.request': return this._handleRequest(packet); // We don't support *incoming* replies (yet) case 'kdeconnect.notification.reply': warning('Not implemented', packet.type); return; default: warning('Unknown notification packet', this.device.name); } } connected() { super.connected(); this.requestNotifications(); } /** * Handle an incoming notification or closed report. */ _handleNotification(packet) { // A report that a remote notification has been dismissed if (packet.body.hasOwnProperty('isCancel')) { this.device.hideNotification(packet.body.id); // A silent notification; process it so we can abort the transfer } else if (packet.body.hasOwnProperty('silent')) { this.silenceNotification(packet); // A normal, remote notification } else { this.receiveNotification(packet); } } /** * Handle an incoming request to close or list notifications. */ _handleRequest(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. } else if (packet.body.hasOwnProperty('cancel')) { let [, type, application, id] = ID_REGEX.exec(packet.body.cancel); switch (type) { case 'fdo': this.service.remove_notification(parseInt(id)); break; case 'gtk': this.service.remove_notification(id, application); break; default: warning('Unknown notification type', this.device.name); } } } /** * Check an internal id for evidence that it's from an SMS app * * @param {string} - Internal notification id * @return {boolean} - Whether the id has evidence it's from an SMS app */ _isSms(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; } /** * Sending Notifications */ async _uploadIcon(packet, icon) { try { // Normalize icon-name strings into GIcons if (typeof icon === 'string') { icon = new Gio.ThemedIcon({name: icon}); } switch (true) { // TODO: Currently we skip icons for bluetooth connections case (this.device.connection_type === 'bluetooth'): return this.device.sendPacket(packet); // GBytesIcon case (icon instanceof Gio.BytesIcon): return this._uploadBytesIcon(packet, icon.get_bytes()); // GFileIcon case (icon instanceof Gio.FileIcon): return this._uploadFileIcon(packet, icon.get_file()); // GThemedIcon case (icon instanceof Gio.ThemedIcon): return this._uploadThemedIcon(packet, icon); default: return this.device.sendPacket(packet); } } catch (e) { logError(e); return this.device.sendPacket(packet); } } /** * A function for uploading named icons from a GLib.Bytes object. * * @param {Core.Packet} packet - The packet for the notification * @param {GLib.Bytes} bytes - The themed icon name */ _uploadBytesIcon(packet, bytes) { return this._uploadIconStream( packet, Gio.MemoryInputStream.new_from_bytes(bytes), bytes.get_size() ); } /** * A function for uploading icons as Gio.File objects * * @param {Core.Packet} packet - The packet for the notification * @param {Gio.File} file - A Gio.File object for the icon */ async _uploadFileIcon(packet, file) { let stream; try { stream = await new Promise((resolve, reject) => { file.read_async(GLib.PRIORITY_DEFAULT, null, (file, res) => { try { resolve(file.read_finish(res)); } catch (e) { reject(e); } }); }); return this._uploadIconStream( packet, stream, file.query_info('standard::size', 0, null).get_size() ); } catch (e) { logError(e); this.device.sendPacket(packet); } } /** * A function for uploading GThemedIcons * * @param {Core.Packet} packet - The packet for the notification * @param {Gio.ThemedIcon} file - The GIcon to upload */ _uploadThemedIcon(packet, icon) { let theme = Gtk.IconTheme.get_default(); for (let name of icon.names) { // kdeconnect-android doesn't support SVGs so find the largest other let info = theme.lookup_icon( name, Math.max.apply(null, theme.get_icon_sizes(name)), Gtk.IconLookupFlags.NO_SVG ); // Send the first icon we find from the options if (info) { return this._uploadFileIcon( packet, Gio.File.new_for_path(info.get_filename()) ); } } // Fallback to icon-less notification return 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 { let transfer = this.device.createTransfer({ input_stream: stream, size: size }); let success = await transfer.upload(packet); if (!success) { this.device.sendPacket(packet); } } catch (e) { debug(e); this.device.sendPacket(packet); } } /** * This is called by the notification listener. * See Notification.Listener._sendNotification() */ async sendNotification(notif) { try { // Sending notifications is forbidden if (!this.settings.get_boolean('send-notifications')) { return; } debug(`(${notif.appName}) ${notif.title}: ${notif.text}`); // TODO: revisit application notification settings let applications = JSON.parse(this.settings.get_string('applications')); // An unknown application if (!applications.hasOwnProperty(notif.appName)) { applications[notif.appName] = { iconName: 'system-run-symbolic', enabled: true }; // Only catch icons for strings and GThemedIcon if (typeof notif.icon === 'string') { applications[notif.appName].iconName = notif.icon; } else if (notif.icon instanceof Gio.ThemedIcon) { applications[notif.appName].iconName = notif.icon.names[0]; } this.settings.set_string( 'applications', JSON.stringify(applications) ); } // An enabled application if (applications[notif.appName].enabled) { let icon = notif.icon || null; delete notif.icon; let packet = { id: 0, type: 'kdeconnect.notification', body: notif }; await this._uploadIcon(packet, icon); } } catch (e) { logError(e); } } /** * Receiving Notifications */ async _downloadIcon(packet) { let file, path, stream, success, transfer; try { if (!packet.payloadTransferInfo) { return null; } // Save the file in the global cache path = GLib.build_filenamev([ gsconnect.cachedir, packet.body.payloadHash || `${Date.now()}` ]); file = Gio.File.new_for_path(path); // Check if we've already downloaded this icon if (file.query_exists(null)) { return new Gio.FileIcon({file: file}); } // Open the file stream = await new Promise((resolve, reject) => { file.replace_async(null, false, 2, 0, null, (file, res) => { try { resolve(file.replace_finish(res)); } catch (e) { reject(e); } }); }); // Download the icon transfer = this.device.createTransfer({ output_stream: stream, size: packet.payloadSize }); success = await transfer.download( packet.payloadTransferInfo.port || packet.payloadTransferInfo.uuid ); // Return the icon if successful, delete on failure if (success) { return new Gio.FileIcon({file: file}); } await new Promise((resolve, reject) => { file.delete_async(GLib.PRIORITY_DEFAULT, null, (file, res) => { try { file.delete_finish(res); } catch (e) { } resolve(); }); }); return null; } catch (e) { debug(e, this.device.name); return null; } } /** * Receive an incoming notification * * @param {kdeconnect.notification} packet - The notification packet */ async receiveNotification(packet) { try { // Set defaults let action = null; let id = packet.body.id; let title = packet.body.appName; let body = `${packet.body.title}: ${packet.body.text}`; let icon = await this._downloadIcon(packet); // Check if this is a 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 } ]) }; } switch (true) { // Special case for Missed Calls case packet.body.id.includes('MissedCall'): title = packet.body.title; body = packet.body.text; icon = icon || new Gio.ThemedIcon({name: 'call-missed-symbolic'}); break; // Special case for SMS notifications case this._isSms(packet.body.id): title = packet.body.title; body = packet.body.text; action = { name: 'replySms', parameter: new GLib.Variant('s', packet.body.title) }; icon = icon || new Gio.ThemedIcon({name: 'sms-symbolic'}); this._sms[packet.body.ticker] = packet.body.id; break; // Ignore 'appName' if it's the same as 'title' case (packet.body.appName === packet.body.title): body = packet.body.text; break; } // If we still don't have an icon use the device icon icon = 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 }); } catch (e) { logError(e); } } /** * Handle a "silent" notification * * @param {kdeconnect.notification} packet - The notification packet */ async silenceNotification(packet) { try { if (!packet.payloadTransferInfo) { return null; } let transfer = this.device.createTransfer({ output_stream: null, size: packet.payloadSize }); // Since we've passed a bogus stream, this will abort the transfer await transfer.download( packet.payloadTransferInfo.port || packet.payloadTransferInfo.uuid ); } catch (e) { debug(e); } } /** * 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) { debug(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) { debug(id); let tickerId = this._sms[id]; if (tickerId) { delete this._sms[id]; id = tickerId; } 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 */ replyNotification(uuid, message, notification) { debug([uuid, message]); // If the message has no content, we're being asked to open the dialog if (message.length === 0) { new NotificationUI.Dialog({ device: this.device, uuid: uuid, notification: notification }); } else { this.device.sendPacket({ type: 'kdeconnect.notification.reply', body: { requestReplyId: uuid, message: message } }); } } /** * Request the remote notifications be sent */ requestNotifications() { this.device.sendPacket({ type: 'kdeconnect.notification.request', body: {request: true} }); } }); gnome-shell-extension-gsconnect-20/src/service/plugins/ping.js000066400000000000000000000033231341554142200247070ustar00rootroot00000000000000'use strict'; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const PluginsBase = imports.service.plugins.base; var Metadata = { label: _('Ping'), 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 */ var Plugin = GObject.registerClass({ GTypeName: 'GSConnectPingPlugin' }, class Plugin extends PluginsBase.Plugin { _init(device) { super._init(device, 'ping'); } handlePacket(packet) { debug(packet); // Notification let notif = { title: this.device.name, body: _('Ping'), icon: new Gio.ThemedIcon({name: `${this.device.icon_name}-symbolic`}) }; 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 = '') { debug(message); let packet = { id: 0, type: 'kdeconnect.ping', body: {} }; if (message.length) { packet.body.message = message; } this.device.sendPacket(packet); } }); gnome-shell-extension-gsconnect-20/src/service/plugins/runcommand.js000066400000000000000000000147321341554142200261230ustar00rootroot00000000000000'use strict'; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const PluginsBase = imports.service.plugins.base; var Metadata = { label: _('Run Commands'), id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.RunCommand', 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'] } } }; /** * RunCommand Plugin * https://github.com/KDE/kdeconnect-kde/tree/master/plugins/remotecommands * https://github.com/KDE/kdeconnect-kde/tree/master/plugins/runcommand */ var Plugin = 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 Plugin extends PluginsBase.Plugin { _init(device) { super._init(device, 'runcommand'); // Local Commands 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']); // Define executeCommand here so since plugin actions are all stateful let executeCommand = new Gio.SimpleAction({ name: 'executeCommand', parameter_type: new GLib.VariantType('s') }); executeCommand.connect('activate', this._activateAction.bind(this)); this.device.add_action(executeCommand); this._gactions.push(executeCommand); } get remote_commands() { return this._remote_commands; } handlePacket(packet) { // A request... if (packet.type === 'kdeconnect.runcommand.request') { // ...for the local command list if (packet.body.hasOwnProperty('requestCommandList')) { this.sendCommandList(); // ...to execute a command } else if (packet.body.hasOwnProperty('key')) { this._handleCommand(packet.body.key); } // A response to a request for the remote command list } else if (packet.type === 'kdeconnect.runcommand') { this._handleCommandList(packet.body.commandList); } } connected() { super.connected(); this.sendCommandList(); this.requestCommandList(); } cacheClear() { this._remote_commands = {}; this.__cache_write(); this.notify('remote-commands'); } cacheLoaded() { if (this.device.connected) { this.connected(); } if (this.device.get_incoming_supported('runcommand.request')) { this._handleCommandList(this.remote_commands); } } /** * Handle a request to execute the local command with the UUID @key * @param {String} key - The UUID of the local command */ _handleCommand(key) { try { let commandList = this.settings.get_value('command-list').full_unpack(); if (!commandList.hasOwnProperty(key)) { throw new Error(`Unknown command: ${key}`); } GLib.spawn_async( null, ['/bin/sh', '-c', commandList[key].command], null, GLib.SpawnFlags.DEFAULT, null ); } 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. */ _handleCommandList(commandList) { this._remote_commands = commandList; this.notify('remote-commands'); let 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 let submenu = new Gio.Menu(); for (let [uuid, info] of commandEntries) { let 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 let 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 this.device.menu.replace_action('commands', item); } /** * 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({ id: 0, type: 'kdeconnect.runcommand.request', body: {key: key} }); } /** * Send a request for the remote command list */ requestCommandList() { this.device.sendPacket({ id: 0, type: 'kdeconnect.runcommand.request', body: {requestCommandList: true} }); } /** * Send the local command list */ sendCommandList() { let commands = this.settings.get_value('command-list').full_unpack(); this.device.sendPacket({ id: 0, type: 'kdeconnect.runcommand', body: {commandList: commands} }); } }); gnome-shell-extension-gsconnect-20/src/service/plugins/sftp.js000066400000000000000000000445141341554142200247350ustar00rootroot00000000000000'use strict'; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const PluginsBase = imports.service.plugins.base; var Metadata = { label: _('SFTP'), id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.SFTP', 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'] } } }; /** * 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 */ var Plugin = GObject.registerClass({ Name: 'GSConnectSFTPPlugin' }, class Plugin extends PluginsBase.Plugin { _init(device) { super._init(device, 'sftp'); // A reusable launcher for silence procs this._launcher = new Gio.SubprocessLauncher({ flags: Gio.SubprocessFlags.STDOUT_SILENCE | Gio.SubprocessFlags.STDERR_SILENCE }); this._directories = {}; this._mount = null; this._mounting = false; } get has_sshfs() { return GLib.find_program_in_path(gsconnect.metadata.bin.sshfs); } get ip() { // Always use the IP from the current connection return this.device.settings.get_string('tcp-host'); } handlePacket(packet) { // Ensure we don't mount on top of an existing mount // FIXME: might need to update old mounts? if (this._mount) { debug('already mounted', this.device.name); return; } if (packet.type === 'kdeconnect.sftp') { this._agnostic_mount(packet.body); } } connected() { super.connected(); // Disable for all bluetooth connections if (this.device.connection_type === 'bluetooth') { this.device.lookup_action('mount').enabled = false; this.device.lookup_action('unmount').enabled = false; // Request a mount; if using sshfs we will "delay-connect" } else { this.mount(); } } disconnected() { super.disconnected(); this.unmount(); } /** * Setup the directories for export with GMenu and store the mountpoint. For * `sshfs` we also ensure the local mountpoint is ready and get the UID/GID. * * TODO: If #607706 (https://bugzilla.gnome.org/show_bug.cgi?id=607706) * is fixed in gvfs we can mount sshfs in $HOME and show in Nautilus * * @param {object} info - The body of a kdeconnect.sftp packet */ async _setup(info) { try { this._user = info.user; this._password = info.password; this._port = info.port; // Ensure mountpoint is ready for sshfs if (this.has_sshfs) { this._mountpoint = GLib.build_filenamev([ gsconnect.runtimedir, this.device.id ]); this._uri = `file://${this._mountpoint}`; let dir = Gio.File.new_for_path(this._mountpoint); try { dir.make_directory_with_parents(null); dir.set_attribute_uint32('unix::mode', 448, 0, null); } catch (e) { } // Grab the uid/gid from the mountpoint await new Promise((resolve, reject) => { dir.query_info_async('unix::*', 0, 0, null, (dir, res) => { try { let finfo = dir.query_info_finish(res); this._uid = finfo.get_attribute_uint32('unix::uid'); this._gid = finfo.get_attribute_uint32('unix::gid'); resolve(); } catch (e) { reject(e); } }); }); // Otherwise just store the mountpoint's URI } else { this._uri = `sftp://${this.ip}:${this._port}/`; // HACK: Test an SFTP mount for this IP in the 1716-1764 range this._uriRegex = new RegExp(`sftp://(${this.ip}):(171[6-9]|17[2-5][0-9]|176[0-4])`); // Ensure the private key is in the keyring await this._sftp_add_identity(); } // If 'multiPaths' is present setup a local URI for each if (info.hasOwnProperty('multiPaths')) { for (let i = 0; i < info.multiPaths.length; i++) { let name = info.pathNames[i]; let path = info.multiPaths[i]; this._directories[name] = this._uri + path; } // If 'multiPaths' is missing use 'path' and assume a Camera folder } else { let uri = this._uri + info.path; this._directories[_('All files')] = uri; this._directories[_('Camera pictures')] = uri + 'DCIM/Camera'; } return Promise.resolve(true); } catch (e) { return Promise.reject(e); } } async _sftp_mount() { try { let op = new Gio.MountOperation({ username: this._user, password: this._password, password_save: Gio.PasswordSave.NEVER }); // We already know the host, so just accept op.connect('ask-question', (op, message, choices) => { op.reply(Gio.MountOperationResult.HANDLED); }); // We set the password, so just accept op.connect('ask-password', (op, message, user, domain, flags) => { op.reply(Gio.MountOperationResult.HANDLED); }); // This is the actual call to mount the device await new Promise((resolve, reject) => { let file = Gio.File.new_for_uri(this._uri); file.mount_enclosing_volume(0, op, null, (file, res) => { try { resolve(file.mount_enclosing_volume_finish(res)); } catch (e) { // Special case when the GMount didn't unmount properly // but is still on the same port and can be reused. if (e.code && e.code === Gio.IOErrorEnum.ALREADY_MOUNTED) { warning(e, this.device.name); resolve(true); // There's a good chance this is a host key verification // error; regardless we'll remove the key for security. } else { this._sftp_remove_host(this._port); reject(e); } } }); }); // Get the GMount from GVolumeMonitor let monitor = Gio.VolumeMonitor.get(); for (let mount of monitor.get_mounts()) { let uri = mount.get_root().get_uri(); // Check if this is our mount if (this._uri === uri) { this._mount = mount; this._mount.connect('unmounted', this.unmount.bind(this)); // Or if it's a stale mount we need to cleanup } else if (this._uriRegex.test(uri)) { warning('Removing stale GMount', this.device.name); await this._sftp_unmount(mount); } } return Promise.resolve(); } catch (e) { return Promise.reject(e); } } _sftp_unmount(mount) { return new Promise((resolve, reject) => { let op = new Gio.MountOperation(); mount.unmount_with_operation(1, op, null, (mount, res) => { try { resolve(mount.unmount_with_operation_finish(res)); } catch (e) { reject(e); } }); }); } /** * Add GSConnect's private key identity to the authentication agent so our * identity can be verified by Android during private key authentication. */ _sftp_add_identity() { let ssh_add = this._launcher.spawnv([ gsconnect.metadata.bin.ssh_add, GLib.build_filenamev([gsconnect.configdir, 'private.pem']) ]); return new Promise((resolve, reject) => { ssh_add.wait_check_async(null, (proc, res) => { try { resolve(proc.wait_check_finish(res)); } catch (e) { reject(e); } }); }); } /** * Remove old host keys from ~/.ssh/known_hosts for this host from the range * used by KDE Connect (1739-1764). * * @param {number} port - The port to remove the host key for */ async _sftp_remove_host(port = 1739) { try { let ssh_keygen = this._launcher.spawnv([ gsconnect.metadata.bin.ssh_keygen, '-R', `[${this.ip}]:${port}` ]); await new Promise((resolve, reject) => { ssh_keygen.wait_check_async(null, (proc, res) => { try { resolve(proc.wait_check_finish(res)); } catch (e) { reject(e); } }); }); debug(`removed host key for [${this.ip}]:${port}`); } catch (e) { warning(e, this.device.name); } } /** * Start the sshfs process and send the password */ async _sshfs_mount() { try { let argv = [ gsconnect.metadata.bin.sshfs, `${this._user}@${this.ip}:/`, this._mountpoint, '-p', this._port.toString(), // 'disable multi-threaded operation' // Fixes file chunks being sent out of order and corrupted '-s', // 'foreground operation' '-f', // Do not use ~/.ssh/config '-F', '/dev/null', // Use the private key from the service certificate '-o', 'IdentityFile=' + gsconnect.configdir + '/private.pem', // Don't prompt for new host confirmation (we know the host) '-o', 'StrictHostKeyChecking=no', // Prevent storing as a known host '-o', 'UserKnownHostsFile=/dev/null', // Match keepalive for kdeconnect connection (30sx3) '-o', 'ServerAliveInterval=30', // Wait until mountpoint is first accessed to connect '-o', 'delay_connect', // Reconnect to server if connection is interrupted '-o', 'reconnect', // Set user/group permissions to allow readwrite access '-o', `uid=${this._uid}`, '-o', `gid=${this._gid}`, // 'read password from stdin (only for pam_mount!)' '-o', 'password_stdin' ]; // Execute sshfs this._mount = new Gio.Subprocess({ argv: argv, flags: Gio.SubprocessFlags.STDIN_PIPE | Gio.SubprocessFlags.STDERR_PIPE }); this._mount.init(null); // Cleanup when the process exits this._mount.wait_async(null, this._sshfs_unmount.bind(this)); // Since we're using '-o reconnect' we watch stderr so we can quit // on errors *we* consider fatal, otherwise the process may dangle let stderr = new Gio.DataInputStream({ base_stream: this._mount.get_stderr_pipe() }); this._sshfs_check(stderr); // Send session password return new Promise((resolve, reject) => { this._mount.get_stdin_pipe().write_all_async( `${this._password}\n`, GLib.PRIORITY_DEFAULT, null, (stream, res) => { try { resolve(stream.write_all_finish(res)); } catch (e) { reject(e); } } ); }); } catch (e) { return Promise.reject(e); } } _sshfs_unmount(proc, res) { // This is the callback for Gio.Subprocess.wait_async() try { proc.wait_finish(res); } catch (e) { // Silence errors } finally { this._mount = null; } // Skip if there's no mountpoint defined if (!this._mountpoint) return Promise.resolve(); // We also call fusermount/umount to ensure it's actually unmounted let argv = [gsconnect.metadata.bin.fusermount, '-uz', this._mountpoint]; // On Linux `fusermount` should be available, but BSD uses `umount` // See: https://phabricator.kde.org/D6945 if (!GLib.find_program_in_path(gsconnect.metadata.bin.fusermount)) { argv = ['umount', this._mountpoint]; } return new Promise ((resolve, reject) => { let umount = this._launcher.spawnv(argv); umount.wait_async(null, (proc, res) => { try { resolve(proc.wait_finish(res)); } catch (e) { // Silence errors resolve(); } }); }); } /** * Watch stderr output from the sshfs process for fatal errors */ _sshfs_check(stream) { stream.read_line_async(GLib.PRIORITY_DEFAULT, null, (stream, res) => { try { let msg = stream.read_line_finish_utf8(res)[0]; if (msg !== null) { if (msg.includes('ssh_dispatch_run_fatal')) { throw new Error(msg); } warning(msg, `${this.device.name}: sshfs`); this._sshfs_check(stream); } } catch (e) { debug(e); this.unmount(); } }); } /** * Replace the 'Mount' item with a submenu of directories */ _addSubmenu() { // Sftp Submenu let submenu = new Gio.Menu(); // Directories Section let directories = new Gio.Menu(); for (let [name, uri] of Object.entries(this._directories)) { directories.append(name, `device.openPath::${uri}`); } submenu.append_section(null, directories); // Unmount Section/Item let unmount = new Gio.Menu(); unmount.add_action(this.device.lookup_action('unmount')); submenu.append_section(null, unmount); // Files Item let item = new Gio.MenuItem(); item.set_detailed_action('device.mount'); // Icon with check emblem // TODO: this emblem often isn't very visible let icon = new Gio.EmblemedIcon({ gicon: new Gio.ThemedIcon({name: 'folder-remote-symbolic'}) }); let emblem = new Gio.Emblem({ icon: new Gio.ThemedIcon({name: 'emblem-default'}) }); icon.add_emblem(emblem); item.set_icon(icon); item.set_attribute_value( 'hidden-when', new GLib.Variant('s', 'action-disabled') ); item.set_label(_('Files')); item.set_submenu(submenu); this.device.menu.replace_action('device.mount', item); } _removeSubmenu() { let index = this.device.menu.remove_action('device.mount'); let action = this.device.lookup_action('mount'); if (action !== null) { this.device.menu.add_action(action, index); } } /** * TODO: Transitional wrapper until Gio is thoroughly tested * * @param {packet} info - The body of a kdeconnect.sftp packet */ async _agnostic_mount(info) { try { // If mounting is already in progress, let that fail before retrying if (this._mounting) return; this._mounting = true; await this._setup(info); // Prefer sshfs if (this.has_sshfs) { await this._sshfs_mount(); // Fallback to Gio } else { debug('sshfs not found: falling back to GMount'); await this._sftp_mount(); } // Populate the menu this._addSubmenu(); this._mounting = false; } catch (e) { logError(e, `${this.device.name}: ${this.name}`); this._mounting = false; this.unmount(); } } /** * Send a request to mount the remote device */ mount() { this.device.sendPacket({ type: 'kdeconnect.sftp.request', body: {startBrowsing: true} }); } /** * Remove the menu items, unmount the filesystem, replace the mount item */ async unmount() { try { debug('unmounting', this.device.name); // Skip since this will always fail if (!this._mount) { // pass // TODO: Transitional wrapper until Gio is thoroughly tested } else if (this.has_sshfs) { await this._sshfs_unmount(null, null); } else { await this._sftp_unmount(this._mount); } } catch (e) { debug(e, this.device.name); // Always reset the state and menu } finally { debug('unmounted', this.device.name); this._directories = {}; this._mount = null; this._mounting = false; this._removeSubmenu(); } } destroy() { this.unmount(); super.destroy(); } }); gnome-shell-extension-gsconnect-20/src/service/plugins/share.js000066400000000000000000000406031341554142200250560ustar00rootroot00000000000000'use strict'; const GdkPixbuf = imports.gi.GdkPixbuf; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const PluginsBase = imports.service.plugins.base; var Metadata = { label: _('Share'), id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.Share', 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 */ var Plugin = GObject.registerClass({ GTypeName: 'GSConnectSharePlugin', }, class Plugin extends PluginsBase.Plugin { _init(device) { super._init(device, 'share'); } connected() { super.connected(); if (this.device.connection_type === 'bluetooth') { this.device.lookup_action('shareFile').enabled = false; } } /** * Get a GFile for @filename in ~/Downloads, with a numbered suffix if it * already exists (eg. `picture.jpg (1)`) * * @param {String} filename - The basename of the file * @return {Gio.File} - A new GFile for the given @filename in ~/Downloads */ _getFile(filename) { let download_dir = GLib.get_user_special_dir( GLib.UserDirectory.DIRECTORY_DOWNLOAD ); // Account for some corner cases with a fallback if (!download_dir || download_dir === GLib.get_home_dir()) { download_dir = GLib.build_filenamev([GLib.get_home_dir(), 'Downloads']); } let path = GLib.build_filenamev([download_dir, filename]); let filepath = path; let copyNum = 0; while (GLib.file_test(filepath, GLib.FileTest.EXISTS)) { copyNum += 1; filepath = `${path} (${copyNum})`; } return Gio.File.new_for_path(filepath); } async _handleFile(packet) { let file, stream, success, transfer; let title, body, iconName; let buttons = []; try { file = this._getFile(packet.body.filename); stream = await new Promise((resolve, reject) => { file.replace_async(null, false, 0, 0, null, (file, res) => { try { resolve(file.replace_finish(res)); } catch (e) { reject(e); } }); }); transfer = this.device.createTransfer({ output_stream: stream, size: packet.payloadSize }); // Notify that we're about to start the transfer this.device.showNotification({ id: transfer.uuid, title: _('Starting Transfer'), // 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'}) }); // Start transfer success = await transfer.download(packet.payloadTransferInfo.port); this.device.hideNotification(transfer.uuid); // We've been asked to open this directly if (success && packet.body.open) { await new Promise((resolve, reject) => { Gio.AppInfo.launch_default_for_uri_async( file.get_uri(), null, null, (src, res) => { try { Gio.AppInfo.launch_default_for_uri_finish(res); } catch (e) { reject(e); } } ); }); return; } // We'll show a notification (success or failure) if (success) { title = _('Transfer Successful'); // TRANSLATORS: eg. Received 'book.pdf' from Google Pixel body = _('Received “%s” from %s').format( packet.body.filename, this.device.name ); buttons = [ { label: _('Open Folder'), action: 'openPath', parameter: new GLib.Variant('s', file.get_parent().get_uri()) }, { label: _('Open File'), action: 'openPath', parameter: new GLib.Variant('s', file.get_uri()) } ]; iconName = 'document-save-symbolic'; } else { 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(null); } this.device.showNotification({ id: transfer.uuid, title: title, body: body, buttons: buttons, icon: new Gio.ThemedIcon({name: iconName}) }); } catch (e) { logError(e, this.device.name); } } _handleUri(packet) { Gio.AppInfo.launch_default_for_uri_async( packet.body.url, null, null, (src, res) => { try { Gio.AppInfo.launch_default_for_uri_finish(res); } catch (e) { logError(e); } } ); } _handleText(packet) { let dialog = new Gtk.MessageDialog({ text: _('Text Shared By %s').format(this.device.name), secondary_text: packet.body.text, 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(); } /** * Packet dispatch */ handlePacket(packet) { debug('Share: handlePacket()'); if (packet.body.hasOwnProperty('filename')) { this._handleFile(packet); } else if (packet.body.hasOwnProperty('text')) { this._handleText(packet); } else if (packet.body.hasOwnProperty('url')) { this._handleUri(packet); } } /** * Remote methods */ share() { let dialog = new FileChooserDialog(this.device); dialog.show(); } /** * Share local file path or URI * * @param {string} path - Local file path or file URI * @param {boolean} open - Whether the file should be opened after transfer */ async shareFile(path, open = false) { let file, stream, success, transfer; let title, body, iconName; try { if (path.startsWith('file://')) { file = Gio.File.new_for_uri(path); } else { file = Gio.File.new_for_path(path); } stream = await new Promise((resolve, reject) => { file.read_async(GLib.PRIORITY_DEFAULT, null, (file, res) => { try { resolve(file.read_finish(res)); } catch (e) { reject(e); } }); }); let info = file.query_info('standard::size', 0, null); transfer = this.device.createTransfer({ input_stream: stream, size: info.get_size() }); // Notify that we're about to start the transfer this.device.showNotification({ id: transfer.uuid, title: _('Starting Transfer'), // 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'}) }); success = await transfer.upload({ id: 0, type: 'kdeconnect.share.request', body: { filename: file.get_basename(), open: open } }); if (success) { 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'; } else { 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) { warning(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({ id: 0, type: 'kdeconnect.share.request', body: {text: text} }); } /** * Share a URI. Generally the remote device opens it with the scheme default * * @param {string} uri - Currently http(s) and tel: URIs are supported */ shareUri(uri) { switch (true) { // Currently only pass http(s)/tel URIs case uri.startsWith('http://'): case uri.startsWith('https://'): case uri.startsWith('tel:'): break; // Redirect local file URIs case uri.startsWith('file://'): return this.sendFile(uri); // Assume HTTPS default: uri = `https://${uri}`; } this.device.sendPacket({ id: 0, type: 'kdeconnect.share.request', body: {url: uri} }); } }); /** A simple FileChooserDialog for sharing files */ var 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 let box = this.get_content_area().get_children()[0].get_children()[0]; let 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); let 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 { let pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( chooser.get_preview_filename(), chooser.get_scale_factor() * 128, -1 ); chooser.preview_widget.pixbuf = pixbuf; chooser.preview_widget.visible = true; chooser.preview_widget_active = true; } catch (e) { chooser.preview_widget.visible = false; chooser.preview_widget_active = false; } } _onUriButtonToggled(button) { let header = this.get_header_bar(); // Show the URL entry if (button.active) { this.extra_widget.sensitive = false; header.set_custom_title(this._uriEntry); this.set_response_sensitive(Gtk.ResponseType.OK, true); // Hide the URL entry } else { header.set_custom_title(null); this.set_response_sensitive( Gtk.ResponseType.OK, this.get_uris().length > 1 ); this.extra_widget.sensitive = true; } } _sendLink(widget) { if (this._uriButton.active && this._uriEntry.text.length) { this.response(1); } } vfunc_response(response_id) { if (response_id === Gtk.ResponseType.OK) { this.get_uris().map(uri => { let parameter = new GLib.Variant( '(sb)', [uri, this.extra_widget.active] ); this.device.activate_action('shareFile', parameter); }); } else if (response_id === 1) { let parameter = new GLib.Variant('s', this._uriEntry.text); this.device.activate_action('shareUri', parameter); } this.destroy(); } }); gnome-shell-extension-gsconnect-20/src/service/plugins/sms.js000066400000000000000000000340321341554142200245550ustar00rootroot00000000000000'use strict'; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const PluginsBase = imports.service.plugins.base; const Messaging = imports.service.ui.messaging; const TelephonyUI = imports.service.ui.telephony; var Metadata = { label: _('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'] }, 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/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:///". */ let _smsParam = "[\\w.!~*'()-]+=(?:[\\w.!~*'()-]|%[0-9A-F]{2})*"; let _telParam = ";[a-zA-Z0-9-]+=(?:[\\w\\[\\]/:&+$.!~*'()-]|%[0-9A-F]{2})+"; let _lenientDigits = '[+]?(?:[0-9A-F*#().-]| (?! )|%20(?!%20))+'; let _lenientNumber = _lenientDigits + '(?:' + _telParam + ')*'; var _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 var _numberRegex = new RegExp( '^' + '(' + _lenientDigits + ')' + // phone number digits '((?:' + _telParam + ')*)' + // followed by optional parameters '$', 'g'); /** * A simple parsing class for sms: URI's (https://tools.ietf.org/html/rfc5724) */ class URI { constructor(uri) { _smsRegex.lastIndex = 0; let [, recipients, query] = _smsRegex.exec(uri); this.recipients = recipients.split(',').map(recipient => { _numberRegex.lastIndex = 0; let [, number, params] = _numberRegex.exec(recipient); if (params) { for (let param of params.substr(1).split(';')) { let [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 (let field of query.split('&')) { let [key, value] = field.split('='); if (key === 'body') { if (this.body) { throw URIError('duplicate "body" field'); } this.body = (value) ? decodeURIComponent(value) : undefined; } } } } toString() { let uri = 'sms:' + this.recipients.join(','); return (this.body) ? uri + '?body=' + escape(this.body) : uri; } } /** * SMS Message event type. Currently all events are TEXT_MESSAGE. * * TEXT_MESSAGE: Has a "body" field which contains pure, human-readable text */ var MessageEvent = { 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 */ var MessageStatus = { UNREAD: 0, READ: 1 }; /** * SMS Message direction. IN/OUT match the 'type' field from the Android App * message packet. * * See: https://developer.android.com/reference/android/provider/Telephony.TextBasedSmsColumns.html * * IN: An incoming message * OUT: An outgoing message */ var MessageType = { ALL: 0, INBOX: 1, SENT: 2, DRAFT: 3, OUTBOX: 4, FAILED: 5 }; /** * 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/ */ var Plugin = GObject.registerClass({ GTypeName: 'GSConnectSMSPlugin', Properties: { 'conversations': GObject.param_spec_variant( 'conversations', 'Conversation List', 'A list of conversations', new GLib.VariantType('aa{sv}'), null, GObject.ParamFlags.READABLE ) } }, class Plugin extends PluginsBase.Plugin { _init(device) { super._init(device, 'sms'); this.conversations = {}; this.cacheProperties(['conversations']); } get window() { if (this.settings.get_boolean('legacy-sms')) { return new TelephonyUI.Dialog({device: this.device}); } if (this._window === undefined) { this._window = new Messaging.Window({ application: this.service, device: this.device }); } return this._window; } handlePacket(packet) { // Currently only one incoming packet type if (packet.type === 'kdeconnect.sms.messages') { this._handleMessages(packet.body.messages); } } cacheClear() { this.conversations = {}; this.__cache_write(); this.notify('conversations'); } cacheLoaded() { this.notify('conversations'); } connected() { super.connected(); this.requestConversations(); } /** * Handle a new single message */ _handleMessage(contact, message) { let conversation = null; if (this._window) { conversation = this._window.getConversation(message.address); } if (conversation) { // Track expected ticker of outgoing messages so they can be closed // FIXME: this is not working well if (message.type === MessageType.SENT) { conversation._notifications.push( `${contact.name}: ${message.body}` ); } conversation.logMessage(message); } } /** * Parse a conversation (thread of messages) and sort them * * @param {object[]} messages - A list of sms message objects from a thread */ async _handleConversation(messages) { try { // If the address is missing this will cause problems... if (!messages[0].address) return; let thread_id = messages[0].thread_id; let conversation = this.conversations[thread_id] || []; let contact = this.device.contacts.query({ number: messages[0].address }); for (let i = 0, len = messages.length; i < len; i++) { let message = messages[i]; // TODO: invalid MessageType if (message.type < 0 || message.type > 5) continue; let extant = conversation.find(msg => msg._id === message._id); if (extant) { Object.assign(extant, message); } else { conversation.push(message); await this._handleMessage(contact, message); } } // Sort and store the conversation this.conversations[thread_id] = conversation.sort((a, b) => { return (a._id < b._id) ? -1 : 1; }); await this.__cache_write(); this.notify('conversations'); } catch (e) { logError(e); } } /** * Handle a response to telephony.request_conversation(s) * * @param {object[]} messages - A list of sms message objects */ async _handleMessages(messages) { try { // If messages is empty there's nothing to do... if (messages.length === 0) return; let thread_ids = messages.map(msg => msg.thread_id); // If there's multiple thread_id's it's a summary of threads if (thread_ids.some(id => id !== thread_ids[0])) { // Prune conversations Object.keys(this.conversations).map(id => { if (!thread_ids.includes(parseInt(id))) { delete this.conversations[id]; } }); // Request each new or newer thread for (let i = 0, len = messages.length; i < len; i++) { let message = messages[i]; let cache = this.conversations[message.thread_id]; // If this is for an existing thread, mark the rest as read if (cache && message.read === MessageStatus.READ) { cache.forEach(message => message.read = MessageStatus.READ); } if (!cache || cache[cache.length - 1]._id < message._id) { this.requestConversation(message.thread_id); } } await this.__cache_write(); this.notify('conversations'); // Otherwise this is single thread or new message } else { await this._handleConversation(messages); } } catch (e) { logError(e); } } /** * Request a list of messages from a single thread. * * @param {Number} thread_id - The thread_id of the conversation 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 } }); } /** * 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')) { let window = this.window; window.present(); window.setMessage(url); // If there are active conversations, show the chooser dialog } else if (Object.values(this.conversations).length > 0) { let window = new Messaging.ConversationChooser({ application: this.service, device: this.device, message: url }); 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 * TODO: we should now reject multi-recipient URIs * * @param {string} uri - The URI the handle (sms:|sms://|sms:///) */ uriSms(uri) { try { uri = new URI(uri); let window = this.window; window.present(); window.address = uri.recipients[0]; // Set the outgoing message if the uri has a body variable if (uri.body) { window.setMessage(uri.body); } } catch (e) { logError(e, `${this.device.name}: "${uri}"`); } } destroy() { if (this._window) { this._window.destroy(); } super.destroy(); } }); gnome-shell-extension-gsconnect-20/src/service/plugins/systemvolume.js000066400000000000000000000126731341554142200265360ustar00rootroot00000000000000'use strict'; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const PluginsBase = imports.service.plugins.base; var Metadata = { label: _('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/ */ var Plugin = GObject.registerClass({ GTypeName: 'GSConnectSystemVolumePlugin' }, class Plugin extends PluginsBase.Plugin { _init(device) { super._init(device, 'systemvolume'); try { // Cache stream properties this._cache = new WeakMap(); // Connect to the mixer this._streamChangedId = this.service.pulseaudio.connect( 'stream-changed', this._sendSink.bind(this) ); this._outputAddedId = this.service.pulseaudio.connect( 'output-added', this._sendSinkList.bind(this) ); this._outputRemovedId = this.service.pulseaudio.connect( 'output-removed', this._sendSinkList.bind(this) ); } catch (e) { this.destroy(); e.name = 'GvcError'; throw e; } } handlePacket(packet) { switch (true) { case packet.body.hasOwnProperty('requestSinks'): this._sendSinkList(); break; case packet.body.hasOwnProperty('name'): this._changeSink(packet); break; } } connected() { super.connected(); this._sendSinkList(); } /** * Handle a request to change an output */ _changeSink(packet) { let stream; for (let sink of this.service.pulseaudio.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 let cache = this._cache.get(stream) || [null, null, null]; if (packet.body.hasOwnProperty('muted')) { cache[1] = packet.body.muted; this._cache.set(stream, cache); stream.change_is_muted(packet.body.muted); } if (packet.body.hasOwnProperty('volume')) { cache[0] = packet.body.volume; this._cache.set(stream, cache); stream.volume = packet.body.volume; stream.push_volume(); } } /** * 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) { let stream = this.service.pulseaudio.lookup_stream_id(id); // Get a cache to check for changes let cache = this._cache.get(stream) || [null, null, null]; switch (true) { // If the port (we show in the description) has changed we have to // send the whole list to show the change case (cache[2] !== stream.display_name): this._sendSinkList(); return; // If only volume and/or mute are set, send a single update case (cache[0] !== stream.volume): case (cache[1] !== stream.is_muted): this._cache.set(stream, [ stream.volume, stream.is_muted, stream.display_name ]); break; // Bail if nothing relevant has changed default: return; } // Send the stream update this.device.sendPacket({ type: 'kdeconnect.systemvolume', body: { name: stream.name, volume: stream.volume, muted: stream.is_muted } }); } /** * Send a list of local sinks */ _sendSinkList() { let sinkList = this.service.pulseaudio.get_sinks().map(sink => { // Cache the sink state this._cache.set(sink, [ sink.volume, sink.is_muted, sink.display_name ]); // return a sinkList entry return { name: sink.name, description: sink.display_name, muted: sink.is_muted, volume: sink.volume, maxVolume: this.service.pulseaudio.get_vol_max_norm() }; }); // Send the sinkList this.device.sendPacket({ id: 0, type: 'kdeconnect.systemvolume', body: { sinkList: sinkList } }); } destroy() { try { this.service.pulseaudio.disconnect(this._streamChangedId); this.service.pulseaudio.disconnect(this._outputAddedId); this.service.pulseaudio.disconnect(this._outputRemovedId); } catch (e) { } super.destroy(); } }); gnome-shell-extension-gsconnect-20/src/service/plugins/telephony.js000066400000000000000000000173261341554142200257710ustar00rootroot00000000000000'use strict'; const GdkPixbuf = imports.gi.GdkPixbuf; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const PluginsBase = imports.service.plugins.base; const TelephonyUI = imports.service.ui.telephony; var Metadata = { label: _('Telephony'), id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.Telephony', incomingCapabilities: ['kdeconnect.telephony'], outgoingCapabilities: [ 'kdeconnect.telephony.request', 'kdeconnect.telephony.request_mute' ], actions: { legacyReply: { label: _('Reply SMS'), icon_name: 'sms-symbolic', parameter_type: new GLib.VariantType('a{sv}'), incoming: ['kdeconnect.telephony'], outgoing: ['kdeconnect.sms.request'] }, muteCall: { 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 */ var Plugin = GObject.registerClass({ GTypeName: 'GSConnectTelephonyPlugin' }, class Plugin extends PluginsBase.Plugin { _init(device) { super._init(device, 'telephony'); } get legacy_sms() { let sms = this.device.lookup_plugin('sms'); return (sms && sms.settings.get_boolean('legacy-sms')); } async handlePacket(packet) { try { // This is the end of a 'ringing' or 'talking' event if (packet.body.isCancel) { let sender = packet.body.contactName || packet.body.phoneNumber; this.device.hideNotification(`${packet.body.event}|${sender}`); this._restoreMediaState(); // Only handle 'ringing' or 'talking' events, leave the notification // plugin to handle 'missedCall' and 'sms' since they're repliable } else if (['ringing', 'talking'].includes(packet.body.event)) { this._handleEvent(packet); // Legacy messaging support } else if (packet.body.event === 'sms' && this.legacy_sms) { this._handleLegacyMessage(packet); } } catch (e) { logError(e); } } /** * Change volume, microphone and media player state in response to an * incoming or answered call. * * @param {String} eventType - 'ringing' or 'talking' */ _setMediaState(eventType) { if (this.service.pulseaudio) { switch (this.settings.get_string(`${eventType}-volume`)) { case 'lower': this.service.pulseaudio.lowerVolume(); break; case 'mute': this.service.pulseaudio.muteVolume(); break; } if (eventType === 'talking' && this.settings.get_boolean('talking-microphone')) { this.service.pulseaudio.muteMicrophone(); } } if (this.service.mpris && this.settings.get_boolean(`${eventType}-pause`)) { this.service.mpris.pauseAll(); } } /** * Restore volume, microphone and media player state (if changed), making * sure to unpause before raising volume. */ _restoreMediaState() { if (this.service.mpris) { this.service.mpris.unpauseAll(); } if (this.service.pulseaudio) { this.service.pulseaudio.restore(); } } /** * Load a Gdk.Pixbuf from base64 encoded data * * @param {string} data - Base64 encoded JPEG data */ _getThumbnailPixbuf(data) { let loader; try { data = GLib.base64_decode(data); loader = new GdkPixbuf.PixbufLoader(); loader.write(data); loader.close(); } catch (e) { warning(e); } return loader.get_pixbuf(); } /** * Show a local notification, possibly with actions * * @param {object} packet - A telephony packet for this event */ _handleEvent(packet) { let body; let buttons = []; let icon = new Gio.ThemedIcon({name: 'call-start-symbolic'}); 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 (packet.body.event === 'ringing') { this._setMediaState('ringing'); // TRANSLATORS: The phone is ringing body = _('Incoming call'); buttons = [{ action: 'muteCall', // TRANSLATORS: Silence the phone ringer 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 }); } _handleLegacyMessage(packet) { let action = null; let icon = new Gio.ThemedIcon({name: 'sms-symbolic'}); // 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 there's a phone number we can make this repliable if (packet.body.phoneNumber) { action = { name: 'legacyReply', parameter: GLib.Variant.full_pack(packet) }; } // Show notification this.device.showNotification({ id: `${packet.body.event}|${sender}`, title: sender, body: packet.body.messageBody, icon: icon, priority: Gio.NotificationPriority.NORMAL, action: action }); } legacyReply(packet) { let window = new TelephonyUI.Dialog({ address: packet.body.phoneNumber, device: this.device, message: { date: packet.id, address: packet.body.phoneNumber, body: packet.body.messageBody, sender: packet.body.contactName || _('Unknown Contact'), type: 1 } }); window.present(); } /** * Silence an incoming call */ muteCall() { this.device.sendPacket({ type: 'kdeconnect.telephony.request_mute', body: {} }); this._restoreMediaState(); } }); gnome-shell-extension-gsconnect-20/src/service/ui/000077500000000000000000000000001341554142200223475ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/src/service/ui/contacts.js000066400000000000000000000352171341554142200245330ustar00rootroot00000000000000'use strict'; const Gdk = imports.gi.Gdk; const GdkPixbuf = imports.gi.GdkPixbuf; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; /** * Return a random color * * @param {*} [salt] - If not %null, will be used as salt for generating a color * @param {Number} alpha - A value in the [0...1] range for the alpha channel * @return {Gdk.RGBA} - A new Gdk.RGBA object generated from the input */ function randomRGBA(salt = null, alpha = 1.0) { let red, green, blue; if (salt !== null) { let 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 {Number} r - A number in the [0.0, 1.0] range for the red value * @param {Number} g - A number in the [0.0, 1.0] range for the green value * @param {Number} b - A number in the [0.0, 1.0] range for the blue value * @return {Number} - ... */ function relativeLuminance(rgba) { let {red, green, blue} = rgba; let R = (red > 0.03928) ? red / 12.92 : Math.pow(((red + 0.055) / 1.055), 2.4); let G = (green > 0.03928) ? green / 12.92 : Math.pow(((green + 0.055) / 1.055), 2.4); let 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 Gdk.RGBA contrasted for the input * See: https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef * * @param {Gdk.RGBA} - A Gdk.RGBA object for the background color * @return {Gdk.RGBA} - A Gdk.RGBA object for the foreground color */ function getFgRGBA(rgba) { let bgLuminance = relativeLuminance(rgba); let lightContrast = (0.07275541795665634 + 0.05) / (bgLuminance + 0.05); let darkContrast = (bgLuminance + 0.05) / (0.0046439628482972135 + 0.05); let value = (darkContrast > lightContrast) ? 0.06 : 0.94; return new Gdk.RGBA({red: value, green: value, blue: value, alpha: 0.5}); } /** * Get Gdk.Pixbuf for @path, allowing the corrupt JPEG's KDE Connect sometimes * sends. This function is synchronous * * @param {string} path - A local file path */ function getPixbuf(path, size = null) { let data, loader; // Catch missing avatar files try { data = GLib.file_get_contents(path)[1]; } catch (e) { warning(e.message, path); return undefined; } // Consider errors from partially corrupt JPEGs to be warnings try { loader = new GdkPixbuf.PixbufLoader(); loader.write(data); loader.close(); } catch (e) { warning(e, path); } let pixbuf = loader.get_pixbuf(); // Scale if requested if (size !== null) { return pixbuf.scale_simple(size, size, GdkPixbuf.InterpType.HYPER); } else { return pixbuf; } } /** * Return a localized string for a phone number and type * See: http://www.ietf.org/rfc/rfc2426.txt * * @param {string} number - A phone number and RFC2426 phone number type * @return {string} - A string like '555-5555・Mobile' */ function getNumberLabel(number) { if (!number.type) return _('%s・Other').format(number.value); switch (true) { case number.type.includes('fax'): // TRANSLATORS: A fax number return _('%s・Fax').format(number.value); case number.type.includes('work'): // TRANSLATORS: A work phone number return _('%s・Work'.format(number.value)); case number.type.includes('cell'): // TRANSLATORS: A mobile or cellular phone number return _('%s・Mobile').format(number.value); case number.type.includes('home'): // TRANSLATORS: A home phone number return _('%s・Home').format(number.value); default: // TRANSLATORS: All other phone number types return _('%s・Other').format(number.value); } } /** * Contact Avatar */ var Avatar = GObject.registerClass({ GTypeName: 'GSConnectContactAvatar' }, class Avatar extends Gtk.DrawingArea { _init(contact) { super._init({ height_request: 32, width_request: 32, visible: true, tooltip_text: contact.name || _('Unknown Contact') }); this._path = contact.avatar; } _loadPixbuf() { if (this._path) { this._pixbuf = getPixbuf(this._path, 32); } if (this._pixbuf === undefined) { this._fallback = true; this.bg_color = randomRGBA(this.tooltip_text); let info = Gtk.IconTheme.get_default().lookup_icon( 'avatar-default', 24, Gtk.IconLookupFlags.FORCE_SYMBOLIC ); this._pixbuf = info.load_symbolic( getFgRGBA(this.bg_color), null, null, null )[0]; } this._offset = (this.width_request - this._pixbuf.width) / 2; } vfunc_draw(cr) { if (this._pixbuf === undefined) { this._loadPixbuf(); } // Clip to a circle cr.arc(16, 16, 16, 0, 2 * Math.PI); cr.clipPreserve(); // Fill the background if we don't have an avatar if (this._fallback) { Gdk.cairo_set_source_rgba(cr, this.bg_color); cr.fill(); } // Draw the avatar/icon Gdk.cairo_set_source_pixbuf(cr, this._pixbuf, this._offset, this._offset); cr.paint(); cr.$dispose(); return Gdk.EVENT_PROPAGATE; } }); var ContactChooser = GObject.registerClass({ GTypeName: 'GSConnectContactChooser', Properties: { 'device': GObject.ParamSpec.object( 'device', 'Device', 'The device associated with this window', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, GObject.Object ), 'store': GObject.ParamSpec.object( 'store', 'Store', 'The contacts store', GObject.ParamFlags.READWRITE, GObject.Object ) }, Signals: { 'number-selected': { flags: GObject.SignalFlags.RUN_FIRST, param_types: [GObject.TYPE_STRING] } }, Template: 'resource:///org/gnome/Shell/Extensions/GSConnect/contacts.ui', Children: [ 'contact-entry', 'contact-list', 'contact-placeholder', 'contact-window' ] }, class ContactChooser extends Gtk.Grid { _init(params) { this.connect_template(); super._init(params); // Make sure we're using the correct contacts store this.device.bind_property( 'contacts', this, 'store', GObject.BindingFlags.DEFAULT ); // Setup the contact list this.contact_list._entry = this.contact_entry.text; this.contact_list.set_filter_func(this._filter); this.contact_list.set_sort_func(this._sort); this.contact_list.set_placeholder(this.contact_placeholder); // Cleanup on ::destroy this.connect('destroy', this._onDestroy); // Initial populate this._populate(); } get store() { return this._store || null; } set store(store) { // Do nothing if the store hasn't changed if (this._store && this._store === store) return; if (this._store) { // Disconnect the current store this._store.disconnect(this._contactAddedId); this._store.disconnect(this._contactRemovedId); this._store.disconnect(this._contactChangedId); // Clear the current list let rows = this.contact_list.get_children(); for (let i = 0, len = rows.length; i < len; i++) { // HACK: temporary mitigator for mysterious GtkListBox leak //row.destroy(); rows[i].run_dispose(); imports.system.gc(); } } // 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) ); // If we're replacing the store we need to repopulate now if (this._store) { this._store = store; this._populate(); // Otherwise we're waiting for _init() to complete } else { this._store = store; } } _onContactAdded(store, id) { let contact = this.store.get_contact(id); this.add_contact(contact); } _onContactRemoved(store, id) { let rows = this.contact_list.get_children(); for (let i = 0, len = rows.length; i < len; i++) { let row = rows[i]; if (row.contact.id === id) { // HACK: temporary mitigator for mysterious GtkListBox leak //row.destroy(); row.run_dispose(); imports.system.gc(); } } } _onContactChanged(store, id) { this._onContactRemoved(store, id); this._onContactAdded(store, id); } _onDestroy(chooser) { chooser.store.disconnect(chooser._contactAddedId); chooser.store.disconnect(chooser._contactRemovedId); chooser.store.disconnect(chooser._contactChangedId); chooser.disconnect_template(); } _onSearchChanged(entry) { this.contact_list._entry = entry.text; let dynamic = this.contact_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 = this.add_contact({ // 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; // ...or if we already do, then update it } else { // Update contact object dynamic.contact.name = entry.text; dynamic.contact.numbers[0].value = entry.text; // Update UI let grid = dynamic.get_child(); let nameLabel = grid.get_child_at(1, 0); nameLabel.label = _('Send to %s').format(entry.text); let numLabel = grid.get_child_at(1, 1); numLabel.label = getNumberLabel(dynamic.contact.numbers[0]); } // ...otherwise remove any dynamic contact that's been created } else if (dynamic && dynamic.__tmp) { dynamic.destroy(); } this.contact_list.invalidate_filter(); this.contact_list.invalidate_sort(); } _onNumberSelected(box, row) { // Reset the contact list this.contact_entry.text = ''; this.contact_list.select_row(null); this.contact_window.vadjustment.value = 0; // Emit the number let address = row.number.value; this.emit('number-selected', address); } _filter(row) { // Dynamic contact always shown if (row.__tmp) return true; let query = row.get_parent()._entry; let queryName = query.toLocaleLowerCase(); let queryNumber = query.toPhoneNumber(); // Show contact if text is substring of name if (row.contact.name.toLocaleLowerCase().includes(queryName)) { return true; // Show contact if text is substring of number } else if (queryNumber.length) { for (let 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; } else if (row2.__tmp) { return 1; } return row1.contact.name.localeCompare(row2.contact.name); } _populate() { // Add each contact let contacts = this.store.contacts; for (let i = 0, len = contacts.length; i < len; i++) { this.add_contact(contacts[i]); } } add_contact(contact) { // HACK: fix missing contact names contact.name = contact.name || _('Unknown Contact'); if (contact.numbers.length === 1) { return this.add_contact_number(contact, 0); } for (let i = 0, len = contact.numbers.length; i < len; i++) { this.add_contact_number(contact, i); } } add_contact_number(contact, index) { let row = new Gtk.ListBoxRow({ activatable: true, selectable: true, visible: true }); row.contact = contact; row.number = contact.numbers[index]; this.contact_list.add(row); let grid = new Gtk.Grid({ margin: 6, column_spacing: 6, visible: true }); row.add(grid); if (index === 0) { let avatar = new Avatar(contact); avatar.valign = Gtk.Align.CENTER; grid.attach(avatar, 0, 0, 1, 2); let nameLabel = new Gtk.Label({ label: contact.name, halign: Gtk.Align.START, hexpand: true, visible: true }); grid.attach(nameLabel, 1, 0, 1, 1); } let numLabel = new Gtk.Label({ label: getNumberLabel(row.number), halign: Gtk.Align.START, hexpand: true, // TODO: rtl inverts margin-start so the number don't align margin_start: (index > 0) ? 38 : 0, margin_end: (index > 0) ? 38 : 0, visible: true }); numLabel.get_style_context().add_class('dim-label'); grid.attach(numLabel, 1, 1, 1, 1); return row; } }); gnome-shell-extension-gsconnect-20/src/service/ui/devel.js000066400000000000000000000373631341554142200240200ustar00rootroot00000000000000'use strict'; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const Gdk = imports.gi.Gdk; const System = imports.system; function getPID() { return Gio.DBus.session.call_sync( 'org.freedesktop.DBus', '/org/freedesktop/DBus', 'org.freedesktop.DBus', 'GetConnectionUnixProcessID', new GLib.Variant('(s)', ['org.gnome.Shell.Extensions.GSConnect']), null, Gio.DBusCallFlags.NONE, -1, null ).deep_unpack()[0]; } var Window = GObject.registerClass({ GTypeName: 'GSConnectDevelWindow', Template: 'resource:///org/gnome/Shell/Extensions/GSConnect/devel.ui', Children: [ 'headerbar', 'stack', 'switcher', 'packet-device', 'packet-direction', 'packet-type', 'packet-body', 'packet-button', 'notification-device', 'notification-id', 'notification-time', 'notification-appname', 'notification-title', 'notification-text', 'notification-ticker', 'notification-requestreplyid', 'notification-isclearable', 'sms-device', 'sms-address', 'sms-body', 'sms-type', 'sms-read', 'sms-id', 'sms-thread-id', 'sms-event-text', 'sms-phonenumber', 'sms-messagebody', 'sms-uri', 'telephony-device', 'telephony-event', 'telephony-name', 'telephony-number', 'telephony-body', 'telephony-duplicate', 'telephony-iscancel', 'telephony-receive', 'heap-path', 'heap-save' ] }, class Window extends Gtk.ApplicationWindow { _init() { this.connect_template(); super._init({ application: Gio.Application.get_default(), visible: true }); // Log & Debug Mode actions this.add_action(gsconnect.settings.create_action('debug')); let openLog = new Gio.SimpleAction({name: 'open-log'}); openLog.connect('activate', this._openLog); this.add_action(openLog); // Watch for device changes this._devicesChangedId = this.application.connect( 'notify::devices', this._onDevicesChanged.bind(this) ); this._onDevicesChanged(); // Validate packet entry this.packet_body.buffer.connect( 'changed', this._onPacketBodyChanged.bind(this) ); // Bind notification id to tooltip this.notification_id.bind_property( 'active-id', this.notification_id, 'tooltip-text', GObject.BindingFlags.SYNC_CREATE ); // Set default heap path this.heap_path.set_current_folder(GLib.get_home_dir()); this.show_all(); } vfunc_delete_event(event) { this.disconnect_template(); this.application.disconnect(this._devicesChangedId); } _onDevicesChanged() { this.packet_device.remove_all(); this.notification_device.remove_all(); this.sms_device.remove_all(); this.telephony_device.remove_all(); for (let device of this.application._devices.values()) { this.packet_device.append(device.id, device.name); this.notification_device.append(device.id, device.name); this.sms_device.append(device.id, device.name); this.telephony_device.append(device.id, device.name); } if (this.application.devices.length > 0) { this.packet_device.active = 0; this.notification_device.active = 0; this.sms_device.active = 0; this.telephony_device.active = 0; } } /** * Toggling Bluetooth for testing purposes */ _onBluetoothEnabled(widget) { if (widget.active) { this.application.bluetooth = new imports.service.bluetooth.ChannelService(); widget.sensitive = false; } } /** * Raw Packet */ _onPacketDestinationChanged(combobox) { this.packet_type.remove_all(); let device = this.application._devices.get(this.packet_device.active_id); if (device === undefined) { return; } if (this.packet_direction.active_id === 'incoming') { let incoming = device.settings.get_strv('incoming-capabilities'); incoming.map(c => this.packet_type.append(c, c)); } else if (this.packet_direction.active_id === 'outgoing') { let outgoing = device.settings.get_strv('outgoing-capabilities'); outgoing.map(c => this.packet_type.append(c, c)); } this.packet_type.active = 0; } _onPacketBodyChanged(buffer) { let style, button; if (buffer === this.packet_body.buffer) { button = this.packet_button; style = button.get_style_context(); } else { button = this.receive_packet_button; style = button.get_style_context(); } if (buffer.text.length < 1) { button.tooltip_text = null; style.remove_class('destructive-action'); } else { try { JSON.parse(buffer.text); button.tooltip_text = null; style.remove_class('destructive-action'); } catch (e) { button.tooltip_text = e.message; style.add_class('destructive-action'); } } } _onPacketExecute(button) { try { let body = {}; if (this.packet_body.buffer.text.length > 0) { body = JSON.parse(this.packet_body.buffer.text); } let device = this.application._devices.get( this.packet_device.active_id ); if (this.packet_direction.active_id === 'outgoing') { device.receivePacket({ id: Date.now(), type: this.packet_type.active_id, body: body }); } else if (this.packet_direction.active_id === 'incoming') { device.sendPacket({ id: Date.now(), type: this.packet_type.active_id, body: body }); } } catch (e) { logError(e); } } /** * Notification */ _onNotificationIdChanged(combobox) { if (this.notification_id.active_id === '0|com.google.android.apps.messaging|0|com.google.android.apps.messaging:sms:22|10109') { this.notification_appname.text = 'Messages'; this.notification_title.text = 'Contact Name'; this.notification_text.text = 'SMS Message Body'; } else if (this.notification_id.active_id === '0|com.google.android.dialer|1|MissedCall_content://call_log/calls/163?allow_voicemails=true|10073') { this.notification_appname.text = 'Phone'; this.notification_title.text = 'Missed call'; this.notification_text.text = 'Contact Name'; } else { this.notification_appname.text = ''; this.notification_title.text = ''; this.notification_text.text = ''; } } _onNotificationTickerChanged(entry) { this.notification_ticker.text = [ this.notification_title.text, this.notification_text.text ].join(': '); } _onNotificationReceive(button) { try { let device = this.application._devices.get( this.notification_device.active_id ); device.receivePacket({ id: Date.now(), type: 'kdeconnect.notification', body: { id: this.notification_id.active_id, time: this.notification_time.text, appName: this.notification_appname.text, title: this.notification_title.text, text: this.notification_text.text, ticker: this.notification_ticker.text, requestReplyId: this.notification_requestreplyid.text, isClearable: this.notification_isclearable.active } }); } catch (e) { logError(e); } } _onNotificationIsCancel(button) { try { let device = this.application._devices.get( this.notification_device.active_id ); device.receivePacket({ id: Date.now(), type: 'kdeconnect.notification', body: { id: this.notification_id.active_id, isCancel: true } }); } catch (e) { logError(e); } } /** * SMS */ _onSmsReceive(button) { let device = this.application._devices.get(this.sms_device.active_id); device.receivePacket({ id: Date.now(), type: 'kdeconnect.sms.messages', body: { messages: [ { address: this.sms_address.text, body: this.sms_body.text, date: Date.now(), type: parseInt(this.sms_type.active_id), read: parseInt(this.sms_read.active_id), _id: this.sms_id.value, thread_id: this.sms_thread_id.value, // FIXME event: (this.sms_event_text.active) ? 0x1 : 0 } ] } }); this.sms_id.value++; } _onSmsSend(button) { let device = this.application._devices.get(this.sms_device.active_id); device.sendPacket({ id: Date.now(), type: 'kdeconnect.sms.request', body: { sendSms: true, phoneNumber: this.sms_phonenumber.text, messageBody: this.sms_messagebody.text } }); } _onSyncContacts(button) { let device = this.application._devices.get(this.sms_device.active_id); device.lookup_plugin('contacts').connected(); } _onDeleteContacts(button) { let device = this.application._devices.get(this.sms_device.active_id); device.lookup_plugin('contacts')._store.clear(); } _onSyncSMS(button) { let device = this.application._devices.get(this.sms_device.active_id); device.lookup_plugin('sms').connected(); } _onDeleteSMS(button) { let device = this.application._devices.get(this.sms_device.active_id); device.lookup_plugin('sms').conversations = {}; device.lookup_plugin('sms').__cache_write(); } _onOpenURI(entry) { if (this.sms_uri.text) { log(this.sms_uri.text); Gio.AppInfo.launch_default_for_uri_async(this.sms_uri.text, null, null, (src, res) => { try { Gio.AppInfo.launch_default_for_uri_finish(res); } catch (e) { logError(e); } }); } } /** * Telephony */ _onTelephonyEventChanged(combobox) { this.telephony_duplicate.sensitive = ['missedCall', 'sms'].includes(combobox.active_id); } _onTelephonyReceive(button) { try { let device = this.application._devices.get( this.telephony_device.active_id ); let nid, nappname, ntime, ntitle, ntext; if (this.telephony_duplicate.active_id !== 0) { if (this.telephony_event.active_id === 'missedCall') { nid = '0|com.google.android.dialer|1|MissedCall_content://call_log/calls/163?allow_voicemails=true|10073'; nappname = 'Phone'; ntitle = 'Missed call'; ntext = (this.telephony_name.text) ? this.telephony_name.text : this.telephony_number.text; } else if (this.telephony_event.active_id === 'sms') { nid = '0|com.google.android.apps.messaging|0|com.google.android.apps.messaging:sms:22|10109'; nappname = 'Messages'; ntitle = (this.telephony_name.text) ? this.telephony_name.text : this.telephony_number.text; ntext = this.telephony_body.text; } ntime = `${Date.now() - 1000}`; } if (this.telephony_duplicate.active_id === 2) { device.receivePacket({ id: Date.now(), type: 'kdeconnect.notification', body: { id: nid, time: ntime, appName: nappname, title: ntitle, text: ntext, ticker: `${ntitle}: ${ntext}`, requestReplyId: '23b1b660-8eb8-4c13-a232-79104300c114', isClearable: true } }); } device.receivePacket({ id: Date.now(), type: 'kdeconnect.telephony', body: { event: this.telephony_event.active_id, phoneNumber: this.telephony_number.text || undefined, contactName: this.telephony_name.text || undefined, messageBody: this.telephony_body.text || undefined } }); if (this.telephony_duplicate.active_id === 1) { device.receivePacket({ id: Date.now(), type: 'kdeconnect.notification', body: { id: nid, time: ntime, appName: nappname, title: ntitle, text: ntext, ticker: `${ntitle}: ${ntext}`, requestReplyId: '23b1b660-8eb8-4c13-a232-79104300c114', isClearable: true } }); } } catch (e) { logError(e); } } _onTelephonyIsCancel(button) { try { let device = this.application._devices.get( this.telephony_device.active_id ); device.receivePacket({ id: Date.now(), type: 'kdeconnect.telephony', body: { event: this.telephony_event.active_id, phoneNumber: this.telephony_number.text, contactName: this.telephony_name.text, messageBody: this.telephony_body.text, isCancel: true } }); } catch (e) { logError(e); } } /** * Logging */ _openLog() { try { let display = Gdk.Display.get_default(); let ctx = display.get_app_launch_context(); Gio.AppInfo.create_from_commandline( 'journalctl -f -o cat GLIB_DOMAIN=GSConnect', 'GSConnect', Gio.AppInfoCreateFlags.NEEDS_TERMINAL ).launch([], ctx); } catch (e) { logError(e); } } /** * System methods */ breakpoint() { log('Debug: System.breakpoint()'); System.breakpoint(); } dumpHeap() { let path = GLib.build_filenamev([ GLib.filename_from_uri(this.heap_path.get_uri())[0], 'gsconnect.heap' ]); let i = 1; while (GLib.file_test(`${path}.${i}`, GLib.FileTest.EXISTS)) { i++; } path = `${path}.${i}`; log(`Debug: System.dumpHeap('${path}')`); System.dumpHeap(path); } gc() { log('Debug: System.gc()'); System.gc(); } }); gnome-shell-extension-gsconnect-20/src/service/ui/device.js000066400000000000000000001021331341554142200241440ustar00rootroot00000000000000'use strict'; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const Pango = imports.gi.Pango; const Keybindings = imports.service.ui.keybindings; // Build a list of plugins and shortcuts for devices const DEVICE_PLUGINS = []; const DEVICE_SHORTCUTS = { activate: ['view-refresh-symbolic', _('Reconnect')], openSettings: ['preferences-system-symbolic', _('Settings')] }; for (let name in imports.service.plugins) { if (name === 'base') continue; // Plugins DEVICE_PLUGINS.push(name); // Shortcuts let meta = imports.service.plugins[name].Metadata; for (let [name, action] of Object.entries(meta.actions)) { if (action.parameter_type === null) { DEVICE_SHORTCUTS[name] = [action.icon_name, action.label]; } } } // A GtkListBoxUpdateHeaderFunc for sections function section_separators(row, before) { if (before) { row.set_header(new Gtk.Separator({visible: true})); } } // A GtkListBoxSortFunc for SectionRow rows function title_sort(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: 'GSConnectSectionRow' }, class SectionRow extends Gtk.ListBoxRow { _init(params) { super._init({ height_request: 56, selectable: false, visible: true }); let grid = new Gtk.Grid({ column_spacing: 12, margin_top: 8, margin_right: 12, margin_bottom: 8, margin_left: 12, visible: true }); this.add(grid); // Row Icon this._icon = new Gtk.Image({ pixel_size: 32 }); grid.attach(this._icon, 0, 0, 1, 2); // Row Title this._title = new Gtk.Label({ halign: Gtk.Align.START, hexpand: true, valign: Gtk.Align.CENTER, vexpand: true }); grid.attach(this._title, 1, 0, 1, 1); // Row Subtitle this._subtitle = new Gtk.Label({ halign: Gtk.Align.START, hexpand: true, valign: Gtk.Align.CENTER, vexpand: true }); this._subtitle.get_style_context().add_class('dim-label'); grid.attach(this._subtitle, 1, 1, 1, 1); Object.assign(this, params); } get icon_name() { return this._icon.gicon.names[0]; } set icon_name(icon_name) { this._icon.visible = (icon_name); this._icon.gicon = new Gio.ThemedIcon({name: icon_name}); } get title() { return this._title.label; } set title(text) { this._title.visible = (text); this._title.label = text; } get subtitle() { return this._subtitle.label; } set subtitle(text) { this._subtitle.visible = (text); this._subtitle.label = text; } get widget() { return this._widget; } set widget(widget) { if (this._widget && this._widget instanceof Gtk.Widget) { this._widget.destroy(); this._widget = null; } this._widget = widget; this.get_child().attach(this.widget, 2, 0, 1, 2); } }); var DevicePreferences = GObject.registerClass({ GTypeName: 'GSConnectDevicePreferences', Template: 'resource:///org/gnome/Shell/Extensions/GSConnect/device.ui', Children: [ 'sidebar', 'stack', 'infobar', // Sharing 'sharing-list', 'sharing-page', 'clipboard', 'clipboard-sync', 'mousepad', 'mpris', 'systemvolume', // RunCommand 'runcommand', 'runcommand-page', 'command-list', 'command-list-placeholder', 'command-toolbar', 'command-add', 'command-remove', 'command-edit', 'command-save', 'command-editor', 'command-name', 'command-line', // 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', 'shortcuts-commands', 'shortcuts-commands-title', 'shortcuts-commands-list', // Advanced 'advanced-page', 'plugin-list', 'experimental-list', 'danger-list' ] }, class DevicePreferences extends Gtk.Grid { _init(device) { this.connect_template(); super._init(); this.device = device; this.set_name(device.id); // Menus this._menus = Gtk.Builder.new_from_resource(gsconnect.app_path + '/gtk/menus.ui'); this._menus.translation_domain = 'org.gnome.Shell.Extensions.GSConnect'; // GSettings this.settings = new Gio.Settings({ settings_schema: gsconnect.gschema.lookup('org.gnome.Shell.Extensions.GSConnect.Device', true), path: '/org/gnome/shell/extensions/gsconnect/device/' + this.device.id + '/' }); // Infobar this.settings.bind( 'paired', this.infobar, 'reveal-child', Gio.SettingsBindFlags.GET | Gio.SettingsBindFlags.INVERT_BOOLEAN ); this._setupActions(); // Device Menu this.menu = this._menus.get_object('device-menu'); this.menu.prepend_section(null, this.device.menu); this.insert_action_group('device', this.device); // Settings Pages this._sharingSettings(); 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})); } }); // Connected/Paired this._bluetoothHostChangedId = this.settings.connect( 'changed::bluetooth-host', this._onBluetoothHostChanged.bind(this) ); this._tcpHostChangedId = this.settings.connect( 'changed::tcp-host', this._onTcpHostChanged.bind(this) ); this._connectedId = this.device.connect( 'notify::connected', this._onConnected.bind(this) ); this._onConnected(this.device); // Hide elements for any disabled plugins for (let name of DEVICE_PLUGINS) { if (this.hasOwnProperty(name)) { this[name].visible = this.get_plugin_allowed(name); } } } get service() { return Gio.Application.get_default(); } get supported_plugins() { let supported = this.settings.get_strv('supported-plugins'); // Preempt 'mousepad' plugin on Wayland if (_WAYLAND) supported.splice(supported.indexOf('mousepad'), 1); return supported; } _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()); } _onToggleRowActivated(box, row) { let widget = row.get_child().get_child_at(1, 0); widget.active = !widget.active; } _onConnected(device) { this._onTcpHostChanged(); this._onBluetoothHostChanged(); } _onBluetoothHostChanged() { let action = this.actions.lookup_action('connect-bluetooth'); let hasBluetooth = (this.settings.get_string('bluetooth-host').length); let isLan = (this.settings.get_string('last-connection') === 'tcp'); action.enabled = (isLan && hasBluetooth); } _onActivateBluetooth() { this.settings.set_string('last-connection', 'bluetooth'); this.device.activate(); } _onTcpHostChanged() { let action = this.actions.lookup_action('connect-tcp'); let hasLan = (this.settings.get_string('tcp-host').length); let isBluetooth = (this.settings.get_string('last-connection') === 'bluetooth'); action.enabled = (isBluetooth && hasLan); } _onActivateLan() { this.settings.set_string('last-connection', 'tcp'); this.device.activate(); } _onEncryptionInfo() { let 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(); } _onDeleteDevice(button) { let application = Gio.Application.get_default(); application.deleteDevice(this.device.id); } _destroy() { this.disconnect_template(); // Keybindings signals this.device.disconnect(this._actionAddedId); this.device.disconnect(this._actionRemovedId); this.settings.disconnect(this._keybindingsId); // Device state signals this.device.disconnect(this._connectedId); this.settings.disconnect(this._bluetoothHostChangedId); this.settings.disconnect(this._tcpHostChangedId); this.settings.disconnect(this._pluginsId); } _getSettings(name) { if (this._gsettings === undefined) { this._gsettings = {}; } if (this._gsettings.hasOwnProperty(name)) { return this._gsettings[name]; } let meta = imports.service.plugins[name].Metadata; this._gsettings[name] = new Gio.Settings({ settings_schema: gsconnect.gschema.lookup(meta.id, -1), path: this.settings.path + 'plugin/' + name + '/' }); return this._gsettings[name]; } _setupActions() { this.actions = new Gio.SimpleActionGroup(); this.insert_action_group('settings', this.actions); let settings = this._getSettings('battery'); this.actions.add_action(settings.create_action('send-statistics')); settings = this._getSettings('clipboard'); this.actions.add_action(settings.create_action('send-content')); this.actions.add_action(settings.create_action('receive-content')); this.clipboard_sync.set_menu_model(this._menus.get_object('clipboard-sync')); settings = this._getSettings('contacts'); this.actions.add_action(settings.create_action('contacts-source')); settings = this._getSettings('mousepad'); this.actions.add_action(settings.create_action('share-control')); settings = this._getSettings('mpris'); this.actions.add_action(settings.create_action('share-players')); settings = this._getSettings('notification'); this.actions.add_action(settings.create_action('send-notifications')); settings = this._getSettings('sms'); this.actions.add_action(settings.create_action('legacy-sms')); settings = this._getSettings('systemvolume'); this.actions.add_action(settings.create_action('share-sinks')); settings = this._getSettings('telephony'); this.actions.add_action(settings.create_action('ringing-volume')); this.actions.add_action(settings.create_action('ringing-pause')); this.ringing_volume.set_menu_model(this._menus.get_object('ringing-volume')); 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')); this.talking_volume.set_menu_model(this._menus.get_object('talking-volume')); // Connect Actions let status_bluetooth = new Gio.SimpleAction({name: 'connect-bluetooth'}); status_bluetooth.connect('activate', this._onActivateBluetooth.bind(this)); this.actions.add_action(status_bluetooth); let status_lan = new Gio.SimpleAction({name: 'connect-tcp'}); status_lan.connect('activate', this._onActivateLan.bind(this)); this.actions.add_action(status_lan); // Pair Actions let encryption_info = new Gio.SimpleAction({name: 'encryption-info'}); encryption_info.connect('activate', this._onEncryptionInfo.bind(this)); this.actions.add_action(encryption_info); let status_pair = new Gio.SimpleAction({name: 'pair'}); status_pair.connect('activate', this.device.pair.bind(this.device)); this.settings.bind('paired', status_pair, 'enabled', 16); this.actions.add_action(status_pair); let status_unpair = new Gio.SimpleAction({name: 'unpair'}); status_unpair.connect('activate', this.device.unpair.bind(this.device)); this.settings.bind('paired', status_unpair, 'enabled', 0); this.actions.add_action(status_unpair); } /** * Sharing Settings */ _sharingSettings() { this.sharing_list.foreach(row => { let name = row.get_name(); row.visible = this.device.get_outgoing_supported(`${name}.request`); // Extra check for battery reporting if (name === 'battery') { row.visible = row.visible && this.service.type === 'laptop'; } }); // Separators & Sorting this.sharing_list.set_header_func(section_separators); this.sharing_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); }); } /** * RunCommand Page */ _runcommandSettings() { // Exclusively enable the editor or add button this.command_editor.bind_property( 'visible', this.command_add, 'sensitive', GObject.BindingFlags.INVERT_BOOLEAN ); // Bind the edit/save button sensitivity to the editor visibility this.command_editor.bind_property( 'visible', this.command_edit, 'sensitive', GObject.BindingFlags.INVERT_BOOLEAN ); this.command_editor.bind_property( 'visible', this.command_save, 'sensitive', GObject.BindingFlags.DEFAULT ); // Scroll with keyboard focus let runcommand_box = this.runcommand_page.get_child().get_child(); runcommand_box.set_focus_vadjustment(this.runcommand_page.vadjustment); // Local Command List let settings = this._getSettings('runcommand'); this._commands = settings.get_value('command-list').full_unpack(); this._commands = (typeof this._commands === 'string') ? {} : this._commands; this.command_list.set_placeholder(this.command_list_placeholder); this.command_list.set_sort_func(title_sort); this.command_list.set_header_func(section_separators); Object.keys(this._commands).map(uuid => this._insertCommand(uuid)); } _resetCommandEditor() { // Reset the command editor delete this.command_editor.uuid; this.command_name.text = ''; this.command_line.text = ''; this.command_editor.visible = false; this.command_list.foreach(child => { if (child === this.command_editor) return; child.visible = true; child.sensitive = true; }); this.command_list.invalidate_sort(); this.command_list.invalidate_headers(); } _insertCommand(uuid) { let row = new SectionRow({ title: this._commands[uuid].name, subtitle: this._commands[uuid].command, selectable: true }); row.set_name(uuid); row._subtitle.ellipsize = Pango.EllipsizeMode.MIDDLE; this.command_list.add(row); return row; } _onCommandSelected(box) { let selected = (box.get_selected_row() !== null); this.command_edit.sensitive = selected; this.command_remove.sensitive = selected; } // The [+] button in the toolbar _onAddCommand(button) { let uuid = GLib.uuid_string_random(); this._commands[uuid] = {name: '', command: ''}; let row = this._insertCommand(uuid); this.command_list.select_row(row); this._onEditCommand(); } // The [-] button in the toolbar _onRemoveCommand(button) { let row = this.command_list.get_selected_row(); delete this._commands[row.get_name()]; this._getSettings('runcommand').set_value( 'command-list', GLib.Variant.full_pack(this._commands) ); row.destroy(); this._resetCommandEditor(); } // 'Edit' icon in the toolbar _onEditCommand(button) { let row = this.command_list.get_selected_row(); let uuid = row.get_name(); this.command_editor.uuid = uuid; this.command_name.text = this._commands[uuid].name; this.command_line.text = this._commands[uuid].command; row.visible = false; this.command_editor.visible = true; this.command_name.has_focus = true; this.command_list.foreach(child => { child.sensitive = (child === this.command_editor); }); } // 'Save' icon in the toolbar _onSaveCommand(button) { let row = this.command_list.get_selected_row(); let uuid = row.get_name(); if (this.command_name.text && this.command_line.text) { this._commands[uuid] = { name: this.command_name.text, command: this.command_line.text }; row.title = this.command_name.text; row.subtitle = this.command_line.text; this._getSettings('runcommand').set_value( 'command-list', GLib.Variant.full_pack(this._commands) ); } else { delete this._commands[uuid]; row.destroy(); } this._resetCommandEditor(); } // The 'folder' icon in the command editor GtkEntry _onBrowseCommand(entry, icon_pos, event) { let filter = new Gtk.FileFilter(); filter.add_mime_type('application/x-executable'); let dialog = new Gtk.FileChooserDialog({filter: filter}); dialog.add_button(_('Cancel'), Gtk.ResponseType.CANCEL); dialog.add_button(_('Open'), Gtk.ResponseType.OK); dialog.set_default_response(Gtk.ResponseType.OK); dialog.connect('response', (dialog, response_id) => { if (response_id === Gtk.ResponseType.OK) { this.command_line.text = dialog.get_filename(); } dialog.destroy(); }); dialog.show_all(); } /** * Notification Settings */ _notificationSettings() { let settings = this._getSettings('notification'); settings.bind( 'send-notifications', this.notification_apps, 'sensitive', Gio.SettingsBindFlags.DEFAULT ); // Scroll with keyboard focus let 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(title_sort); this.notification_apps.set_header_func(section_separators); this._populateApplications(settings); } _onNotificationRowActivated(box, row) { let settings = this._getSettings('notification'); let applications = {}; try { applications = JSON.parse(settings.get_string('applications')); } catch (e) { applications = {}; } applications[row.title].enabled = !applications[row.title].enabled; row.widget.label = applications[row.title].enabled ? _('On') : _('Off'); settings.set_string('applications', JSON.stringify(applications)); } _populateApplications(settings) { let applications = this._queryApplications(settings); for (let name in applications) { let row = new SectionRow({ icon_name: applications[name].iconName, title: name, height_request: 48, widget: new Gtk.Label({ label: applications[name].enabled ? _('On') : _('Off'), margin_start: 12, margin_end: 12, halign: Gtk.Align.END, valign: Gtk.Align.CENTER, vexpand: true, visible: true }) }); this.notification_apps.add(row); } } // TODO: move to components/notification.js _queryApplications(settings) { let applications = {}; try { applications = JSON.parse(settings.get_string('applications')); } catch (e) { applications = {}; } let appInfos = []; let ignoreId = 'org.gnome.Shell.Extensions.GSConnect.desktop'; // Query GNOME's notification settings for (let appSettings of Object.values(this.service.notification.applications)) { let appId = appSettings.get_string('application-id'); if (appId !== ignoreId) { let appInfo = Gio.DesktopAppInfo.new(appId); if (appInfo) { appInfos.push(appInfo); } } } // Scan applications that statically declare to show notifications // TODO: if g-s-d does this already, maybe we don't have to for (let appInfo of Gio.AppInfo.get_all()) { if (appInfo.get_id() !== ignoreId && appInfo.get_boolean('X-GNOME-UsesNotifications')) { appInfos.push(appInfo); } } // Update GSettings for (let appInfo of appInfos) { let appName = appInfo.get_name(); if (appName && !applications[appName]) { 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(section_separators); this.talking_list.set_header_func(section_separators); } /** * Keyboard Shortcuts */ _keybindingSettings() { // Scroll with keyboard focus let 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(section_separators); this.shortcuts_actions_list.set_sort_func(title_sort); // Init for (let name in DEVICE_SHORTCUTS) { this._addPluginKeybinding(name); } this._setPluginKeybindings(); // Watch for GAction and Keybinding changes this._actionAddedId = this.device.connect( 'action-added', () => this.shortcuts_actions_list.invalidate_filter() ); this._actionRemovedId = this.device.connect( 'action-removed', () => this.shortcuts_actions_list.invalidate_filter() ); this._keybindingsId = this.settings.connect( 'changed::keybindings', this._setPluginKeybindings.bind(this) ); // TODO: probably not used very often, but needs work this.shortcuts_commands_list.set_header_func(section_separators); this.shortcuts_commands_list.set_sort_func(title_sort); this._populateCommandKeybindings(); } _addPluginKeybinding(name) { let [icon_name, label] = DEVICE_SHORTCUTS[name]; let widget = new Gtk.Label({ label: _('Disabled'), visible: true }); widget.get_style_context().add_class('dim-label'); let row = new SectionRow({ icon_name: icon_name, title: label, widget: widget }); row.height_request = 48; row._icon.pixel_size = 16; row.action = name; this.shortcuts_actions_list.add(row); } _filterPluginKeybindings(row) { return (this.device.lookup_action(row.action)); } _setPluginKeybindings() { let keybindings = this.settings.get_value('keybindings').deep_unpack(); this.shortcuts_actions_list.foreach(row => { if (keybindings[row.action]) { let accel = Gtk.accelerator_parse(keybindings[row.action]); row.widget.label = Gtk.accelerator_get_label(...accel); } else { row.widget.label = _('Disabled'); } }); } _onResetActionShortcuts(button) { let keybindings = this.settings.get_value('keybindings').deep_unpack(); for (let action in keybindings) { if (!action.includes('::')) { delete keybindings[action]; } } this.settings.set_value( 'keybindings', new GLib.Variant('a{ss}', keybindings) ); } _addCommandKeybinding(uuid, command, keybindings) { let action = `executeCommand::${uuid}`; let widget = new Gtk.Label({ label: _('Disabled'), visible: true }); widget.get_style_context().add_class('dim-label'); if (keybindings[action]) { let accel = Gtk.accelerator_parse(keybindings[action]); widget.label = Gtk.accelerator_get_label(...accel); } let row = new SectionRow({ title: command.name, subtitle: command.command, widget: widget }); row.action = action; this.shortcuts_commands_list.add(row); } _populateCommandKeybindings() { this.shortcuts_commands_list.foreach(row => { // HACK: temporary mitigator for mysterious GtkListBox leak //row.destroy(); row.run_dispose(); imports.system.gc(); }); let keybindings = this.settings.get_value('keybindings').deep_unpack(); // Exclude defunct commands for (let action in keybindings) { if (action.includes('::')) { let uuid = action.split('::')[1]; if (!remoteCommands.hasOwnProperty(uuid)) { delete keybindings[action]; } } } // Commands let runcommand = this.device.lookup_plugin('runcommand'); let remoteCommands = (runcommand) ? runcommand.remote_commands : {}; let hasCommands = (Object.keys(remoteCommands).length > 0); this.shortcuts_commands_title.visible = hasCommands; this.shortcuts_commands.visible = hasCommands; for (let [uuid, command] of Object.entries(remoteCommands)) { this._addCommandKeybinding(uuid, command, keybindings); } for (let action in keybindings) { if (action.includes('::')) { let uuid = action.split('::')[1]; if (!remoteCommands.hasOwnProperty(uuid)) { delete keybindings[action]; } } } } _onResetCommandShortcuts(button) { let keybindings = this.settings.get_value('keybindings').deep_unpack(); for (let action in keybindings) { if (action.includes('::')) { delete keybindings[action]; } } this.settings.set_value( 'keybindings', new GLib.Variant('a{ss}', keybindings) ); } async _onShortcutRowActivated(box, row) { try { let keybindings = this.settings.get_value('keybindings').deep_unpack(); let accelerator = await Keybindings.get_accelerator( row.title, keybindings[row.action] ); if (accelerator) { keybindings[row.action] = accelerator; } 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 let advanced_box = this.advanced_page.get_child().get_child(); advanced_box.set_focus_vadjustment(this.advanced_page.vadjustment); // this.plugin_list.set_header_func(section_separators); // Continue focus chain between lists this.plugin_list.next = this.experimental_list; this.experimental_list.prev = this.plugin_list; this._pluginsId = this.settings.connect( 'changed::supported-plugins', this._populatePlugins.bind(this) ); this._populatePlugins(); } get_plugin_allowed(name) { let disabled = this.settings.get_strv('disabled-plugins'); let supported = this.supported_plugins; return supported.filter(name => !disabled.includes(name)).includes(name); } _addPlugin(name) { let plugin = imports.service.plugins[name]; let row = new Gtk.ListBoxRow({ border_width: 0, visible: true }); let grid = new Gtk.Grid({ height_request: 32, visible: true }); row.add(grid); let widget = new Gtk.CheckButton({ label: plugin.Metadata.label, active: this.get_plugin_allowed(name), hexpand: true, tooltip_text: name, valign: Gtk.Align.CENTER, vexpand: true, visible: true }); grid.add(widget); if (plugin.Plugin.prototype.cacheClear) { let button = new Gtk.Button({ image: new Gtk.Image({ icon_name: 'edit-clear-all-symbolic', pixel_size: 16, visible: true }), valign: Gtk.Align.CENTER, vexpand: true, visible: true }); button.connect('clicked', this._clearPluginCache.bind(this, name)); button.get_style_context().add_class('flat'); widget.bind_property('active', button, 'sensitive', 2); grid.add(button); } this.plugin_list.add(row); widget._togglePluginId = widget.connect( 'notify::active', this._togglePlugin.bind(this) ); if (this.hasOwnProperty(name)) { this[name].visible = widget.active; } } _clearPluginCache(name) { try { this.device.lookup_plugin(name).cacheClear(); } catch (e) { warning(e, `${this.device.name}: ${this.name}`); } } _populatePlugins() { let supported = this.supported_plugins; for (let row of this.plugin_list.get_children()) { let checkbutton = row.get_child().get_child_at(0, 0); let name = checkbutton.tooltip_text; if (supported.includes(name)) { row.visible = true; checkbutton.active = this.get_plugin_allowed(name); } else { row.visible = false; if (this.hasOwnProperty(name)) { this[name].visible = false; } } supported.splice(supported.indexOf(name), 1); } for (let name of supported) { this._addPlugin(name); } } _togglePlugin(widget) { try { let name = widget.tooltip_text; let disabled = this.settings.get_strv('disabled-plugins'); if (disabled.includes(name)) { disabled.splice(disabled.indexOf(name), 1); } else { disabled.push(name); } this.settings.set_strv('disabled-plugins', disabled); if (this.hasOwnProperty(name)) { this[name].visible = !disabled.includes(name); } } catch (e) { logError(e); } } }); gnome-shell-extension-gsconnect-20/src/service/ui/keybindings.js000066400000000000000000000253521341554142200252220ustar00rootroot00000000000000'use strict'; const Gdk = imports.gi.Gdk; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; /** * Response enum for ShortcutChooserDialog */ var ResponseType = { CANCEL: Gtk.ResponseType.CANCEL, SET: Gtk.ResponseType.APPLY, UNSET: 2 }; /** * A simplified version of the shortcut editor from GNOME Control Center */ var ShortcutChooserDialog = GObject.registerClass({ GTypeName: 'ShortcutChooserDialog' }, class ShortcutChooserDialog extends Gtk.Dialog { _init(params) { super._init({ transient_for: Gio.Application.get_default().get_active_window(), use_header_bar: true, modal: true, // TRANSLATORS: Title of keyboard shortcut dialog title: _('Set Shortcut') }); this.seat = Gdk.Display.get_default().get_default_seat(); // Content let content = this.get_content_area(); content.spacing = 18; content.margin = 12; // Action Buttons this.cancel_button = this.add_button(_('Cancel'), ResponseType.CANCEL); this.cancel_button.visible = false; // TRANSLATORS: Button to confirm the new shortcut this.set_button = this.add_button(_('Set'), ResponseType.SET); this.set_button.visible = false; this.set_default_response(ResponseType.SET); let summaryLabel = new Gtk.Label({ // TRANSLATORS: Summary of a keyboard shortcut function // Example: Enter a new shortcut to change Messaging label: _('Enter a new shortcut to change %s').format( params.summary ), use_markup: true, visible: true }); content.add(summaryLabel); this.stack = new Gtk.Stack({ transition_type: Gtk.StackTransitionType.CROSSFADE, visible: true }); content.add(this.stack); // Edit page let editPage = new Gtk.Grid({ row_spacing: 18, visible: true }); this.stack.add_named(editPage, 'edit'); let editImage = new Gtk.Image({ resource: '/org/gnome/Shell/Extensions/GSConnect/enter-keyboard-shortcut.svg', visible: true }); editPage.attach(editImage, 0, 0, 1, 1); let editLabel = new Gtk.Label({ // TRANSLATORS: Keys for cancelling (␛) or resetting (␈) a shortcut label: _('Press Esc to cancel or Backspace to reset the keyboard shortcut.'), visible: true }); editLabel.get_style_context().add_class('dim-label'); editPage.attach(editLabel, 0, 1, 1, 1); // Confirm page let confirmPage = new Gtk.Grid({ row_spacing: 18, visible: true }); this.stack.add_named(confirmPage, 'confirm'); this.shortcut_label = new Gtk.ShortcutLabel({ accelerator: params.accelerator, hexpand: true, halign: Gtk.Align.CENTER, visible: true }); confirmPage.attach(this.shortcut_label, 0, 0, 1, 1); this.conflict_label = new Gtk.Label({ hexpand: true, halign: Gtk.Align.CENTER }); confirmPage.attach(this.conflict_label, 0, 1, 1, 1); } get accelerator() { return this.shortcut_label.accelerator; } set accelerator(value) { this.shortcut_label.accelerator = 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: Remove modifier keys let mods = [ 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 ]; if (mods.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 { let available = await check_accelerator(this.accelerator); this.set_button.visible = available; this.conflict_label.visible = !available; } catch (e) { logError(e); this.response(ResponseType.CANCEL); } } _grab() { let seat = Gdk.Display.get_default().get_default_seat(); let success = 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); } let device = seat.get_keyboard() || seat.get_pointer(); if (!device) { 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} - An accelerator * @param {number} - Flags * @param {boolean} - %true if available, %false on error or unavailable */ async function check_accelerator(accelerator, flags = 0) { let action; let result = false; try { // Use gnome-shell's DBus interface to try and grab the accelerator action = await new Promise((resolve, reject) => { Gio.DBus.session.call( 'org.gnome.Shell', '/org/gnome/Shell', 'org.gnome.Shell', 'GrabAccelerator', new GLib.Variant('(su)', [accelerator, flags]), null, Gio.DBusCallFlags.NONE, -1, null, (connection, res) => { try { res = connection.call_finish(res); resolve(res.deep_unpack()[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.deep_unpack()[0]); } catch (e) { reject(e); } } ); }); } return result; } catch (e) { return false; } } /** * Show a dialog to get a keyboard shortcut from a user. * * @param {string} summary - A description of the keybinding's function * @param {string} accelerator - An accelerator as taken by Gtk.ShortcutLabel * @return {string} - An accelerator or %null if it should be unset. */ async function get_accelerator(summary, accelerator = null) { let dialog; try { 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; } } gnome-shell-extension-gsconnect-20/src/service/ui/messaging.js000066400000000000000000000755641341554142200247030ustar00rootroot00000000000000'use strict'; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const Pango = imports.gi.Pango; const Contacts = imports.service.ui.contacts; const Sms = imports.service.plugins.sms; /** * Return a human-readable timestamp. * * @param {Number} time - Milliseconds since the epoch (local time) * @return {String} - A timestamp similar to what Android Messages uses */ function getTime(time) { time = GLib.DateTime.new_from_unix_local(time / 1000); let now = GLib.DateTime.new_now_local(); let diff = now.difference(time); switch (true) { // Super recent case (diff < GLib.TIME_SPAN_MINUTE): // TRANSLATORS: Less than a minute ago return _('Just now'); // Under an hour case (diff < GLib.TIME_SPAN_HOUR): // TRANSLATORS: Time duration in minutes (eg. 15 minutes) return ngettext('%d minute', '%d minutes', (diff / GLib.TIME_SPAN_MINUTE)).format(diff / GLib.TIME_SPAN_MINUTE); // Yesterday, but less than 24 hours ago case (diff < GLib.TIME_SPAN_DAY && (now.get_day_of_month() !== time.get_day_of_month())): // TRANSLATORS: Yesterday, but less than 24 hours (eg. Yesterday · 11:29 PM) return _('Yesterday・%s').format(time.format('%l:%M %p')); // Less than a day ago case (diff < GLib.TIME_SPAN_DAY): return time.format('%l:%M %p'); // Less than a week ago case (diff < (GLib.TIME_SPAN_DAY * 7)): return time.format('%A・%l:%M %p'); default: return time.format('%b %e'); } } function getShortTime(time) { time = GLib.DateTime.new_from_unix_local(time / 1000); let diff = GLib.DateTime.new_now_local().difference(time); switch (true) { case (diff < GLib.TIME_SPAN_MINUTE): // TRANSLATORS: Less than a minute ago return _('Just now'); case (diff < GLib.TIME_SPAN_HOUR): // TRANSLATORS: Time duration in minutes (eg. 15 minutes) return ngettext('%d minute', '%d minutes', (diff / GLib.TIME_SPAN_MINUTE)).format(diff / GLib.TIME_SPAN_MINUTE); // Less than a day ago case (diff < GLib.TIME_SPAN_DAY): return time.format('%l:%M %p'); case (diff < (GLib.TIME_SPAN_DAY * 7)): return time.format('%a'); default: return time.format('%b %e'); } } /** * A simple GtkLabel subclass with a chat bubble appearance */ var ConversationMessage = GObject.registerClass({ GTypeName: 'GSConnectConversationMessage' }, class ConversationMessage extends Gtk.Label { _init(message) { this.message = message; let incoming = (message.type === Sms.MessageType.INBOX); super._init({ label: message.body.linkify(message.date), halign: incoming ? Gtk.Align.START : Gtk.Align.END, selectable: true, tooltip_text: getTime(message.date), use_markup: true, visible: true, wrap: true, wrap_mode: Pango.WrapMode.WORD_CHAR, xalign: 0 }); if (incoming) { this.get_style_context().add_class('message-in'); } else { this.get_style_context().add_class('message-out'); } } vfunc_activate_link(uri) { Gtk.show_uri_on_window( this.get_toplevel(), uri.includes('://') ? uri : `http://${uri}`, Gtk.get_current_event_time() ); return true; } vfunc_query_tooltip(x, y, keyboard_tooltip, tooltip) { if (super.vfunc_query_tooltip(x, y, keyboard_tooltip, tooltip)) { tooltip.set_text(getTime(this.message.date)); return true; } return false; } }); /** * A ListBoxRow for a preview of a conversation */ const ConversationSummary = GObject.registerClass({ GTypeName: 'GSConnectConversationSummary' }, class ConversationSummary extends Gtk.ListBoxRow { _init(contact, message) { super._init({visible: true}); // Row layout let grid = new Gtk.Grid({ margin: 6, column_spacing: 6, visible: true }); this.add(grid); // Contact Avatar this._avatar = new Contacts.Avatar(contact); grid.attach(this._avatar, 0, 0, 1, 3); // Contact Name this._name = new Gtk.Label({ halign: Gtk.Align.START, hexpand: true, ellipsize: Pango.EllipsizeMode.END, use_markup: true, xalign: 0, visible: true }); grid.attach(this._name, 1, 0, 1, 1); // Message Time this._time = new Gtk.Label({ halign: Gtk.Align.END, ellipsize: Pango.EllipsizeMode.END, use_markup: true, xalign: 0, visible: true }); this._time.get_style_context().add_class('dim-label'); grid.attach(this._time, 2, 0, 1, 1); // Message Body this._body = new Gtk.Label({ halign: Gtk.Align.START, ellipsize: Pango.EllipsizeMode.END, use_markup: true, xalign: 0, visible: true }); grid.attach(this._body, 1, 1, 2, 1); this.contact = contact; this.message = message; } get id() { return this._message.thread_id; } get message() { return this._message; } set message(message) { this._message = message; // Contact Name & Message body let nameLabel = this.contact.name; 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.MessageType.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 = '' + nameLabel + ''; bodyLabel = '' + bodyLabel + ''; } // Set the labels, body always smaller this._name.label = nameLabel; this._body.label = '' + bodyLabel + ''; // Time let timeLabel = '' + getShortTime(message.date) + ''; this._time.label = timeLabel; } update() { let timeLabel = '' + getShortTime(this.message.date) + ''; this._time.label = timeLabel; } }); const ConversationWidget = GObject.registerClass({ GTypeName: 'GSConnectConversationWidget', Properties: { 'device': GObject.ParamSpec.object( 'device', 'Device', 'The device associated with this conversation', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, GObject.Object ), 'address': GObject.ParamSpec.string( 'address', 'Address', 'The target phone number or other address', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, '' ), 'has-pending': GObject.ParamSpec.boolean( 'has-pending', 'Has Pending', 'Whether there are sent messages pending confirmation', GObject.ParamFlags.READABLE, false ) }, Template: 'resource:///org/gnome/Shell/Extensions/GSConnect/conversation.ui', Children: [ 'message-entry', 'message-list', 'message-window', 'pending', 'pending-box' ] }, class ConversationWidget extends Gtk.Grid { _init(params) { this.connect_template(); super._init(params); this.device.bind_property( 'connected', this.message_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) ); // Cleanup on ::destroy this.connect('destroy', this._onDestroy); // Pending messages this.pending.id = GLib.MAXUINT32; this.bind_property( 'has-pending', this.pending, 'visible', GObject.BindingFlags.DEFAULT ); this._notifications = []; // Message List this.message_list.set_header_func(this._headerMessages); this.message_list.set_sort_func(this._sortMessages); this._populateMessages(); // HACK: This property was added in gtk-3.24; if it's not present this // will just become a useless JS variable instead of choking this.message_entry.enable_emoji_completion = true; } get address() { if (this._address === undefined) { this._address = null; } return this._address; } set address(value) { this._address = value; } get contact() { // Ensure we have a contact and hold a reference to it if (!this._contact) { this._contact = this.device.contacts.query({number: this.address}); } return this._contact; } get has_pending() { return (this.pending_box.get_children().length); } get sms() { if (this._sms === undefined) { this._sms = this.device.lookup_plugin('sms'); } return this._sms; } _onConnected(device) { if (device.connected) { this.pending_box.foreach(msg => msg.destroy()); } } _onDestroy(conversation) { conversation.device.disconnect(conversation._connectedId); conversation.disconnect_template(); conversation.message_list.foreach(message => { // HACK: temporary mitigator for mysterious GtkListBox leak message.run_dispose(); imports.system.gc(); }); } /** * Messages */ _populateMessages() { this.__first = null; this.__last = null; this.__pos = 0; this.__messages = []; // Try and find a conversation for this number let number = this.address.toPhoneNumber(); let thread_id = null; for (let thread of Object.values(this.sms.conversations)) { let tnumber = thread[0].address.toPhoneNumber(); if (number.endsWith(tnumber) || tnumber.endsWith(number)) { thread_id = thread[0].thread_id; break; } } if (this.sms.conversations[thread_id]) { this.__messages = this.sms.conversations[thread_id].slice(0); this._populateBack(); } } /** * Populate messages in reverse, in chunks of series */ _populateBack() { // Keep track of direction and date let date, direction; while (this.__messages.length > 0) { let message = this.__messages[this.__messages.length - 1]; // TODO: Unsupported MessageType if (message.type !== 1 && message.type !== 2) { this.__messages.pop(); continue; } date = date || message.date; direction = direction || message.type; // Break if we're definitely at the end of series if (message.type !== direction) break; // Start a new series if this is the first if (!this.__first) { this.__first = this._createSeries(message); this.__last = this.__first; this.message_list.prepend(this.__first); // ...or there's more than an hour difference } else if (date - message.date > GLib.TIME_SPAN_HOUR / 1000) { this.__first = this._createSeries(message); this.message_list.prepend(this.__first); // ...or it's in a different direction } else if (message.type !== this.__first.type) { this.__first = this._createSeries(message); this.message_list.prepend(this.__first); } // Create a message, set the message id and prepend it let widget = new ConversationMessage(this.__messages.pop()); this.__first.id = message._id; this.__first.messages.pack_end(widget, false, false, 0); // update the date tracker date = message.date; } } _headerMessages(row, before) { // Skip pending if (row.get_name() === 'pending') return; // Check if the last series was more than an hour ago if (before && (row.date - before.date) > GLib.TIME_SPAN_HOUR / 1000) { let header = new Gtk.Label({ label: getTime(row.date), visible: true }); header.get_style_context().add_class('dim-label'); row.set_header(header); } } _sortMessages(row1, row2) { if (row1.id > row2.id) { return 1; } return -1; } // message-entry::focus-in-event // FIXME: this is not working well _onMessageAcknowledged() { if (this.message_entry.has_focus) { let notification = this.device.lookup_plugin('notification'); if (notification) { while (this._notifications.length > 0) { notification.closeNotification(this._notifications.pop()); } } } } // message-list::size-allocate _onMessageLogged(listbox, allocation) { let vadj = this.message_window.vadjustment; // Try loading more messages if there's room if (vadj.get_upper() <= vadj.get_page_size()) { this._populateBack(); this.message_window.get_child().check_resize(); // We've been asked to hold the position } else if (this.__pos) { vadj.set_value(vadj.get_upper() - this.__pos); this.__pos = 0; // Otherwise scroll to the bottom } else { vadj.set_value(vadj.get_upper() - vadj.get_page_size()); } this._onMessageAcknowledged(); } // message-window::edge-reached _onMessageRequested(scrolled_window, pos) { if (pos === Gtk.PositionType.TOP) { this.__pos = this.message_window.vadjustment.get_upper(); this._populateBack(); } } /** * Add a new row representing a series of sequential messages from one * contact with a single instance of their avatar. * * @param {object} message - The message object to create a series for */ _createSeries(message) { let incoming = (message.type === Sms.MessageType.INBOX); let row = new Gtk.ListBoxRow({ activatable: false, selectable: false, hexpand: true }); row.date = message.date; row.id = message._id; row.type = message.type; let layout = new Gtk.Box({ can_focus: false, hexpand: true, margin: 6, spacing: 6, halign: incoming ? Gtk.Align.START : Gtk.Align.END }); row.add(layout); // Add avatar for incoming messages if (incoming) { let avatar = new Contacts.Avatar(this.contact); avatar.valign = Gtk.Align.END; layout.add(avatar); } // Messages row.messages = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, spacing: 3, halign: layout.halign, // Avatar width (32px) + layout spacing (6px) + 6px margin_end: incoming ? 44 : 0, margin_start: incoming ? 0 : 44 }); layout.add(row.messages); row.show_all(); return row; } /** * Log a new message in the conversation * * @param {Object} message - A sms message object */ logMessage(message) { // TODO: Unsupported MessageType if (message.type !== 1 && message.type !== 2) return; // Ensure it's older than the last message (or the first) // TODO: with a lot of work we could probably handle this... if (this.__last && this.__last.id > message._id) { warning('SMS message out of order', this.device.name); return; } // Start a new series if this is the first if (!this.__first) { this.__first = this._createSeries(message); this.__last = this.__first; this.message_list.add(this.__first); // ...or there's more than an hour difference } else if (message.date - this.__last.date > GLib.TIME_SPAN_HOUR / 1000) { this.__last = this._createSeries(message); this.message_list.add(this.__last); // ...or it's in a different direction } else if (message.type !== this.__last.type) { this.__last = this._createSeries(message); this.message_list.add(this.__last); } // Create a message, set the message id/date and append it let widget = new ConversationMessage(message); this.__last.id = message._id; this.__last.date = message.date; this.__last.messages.pack_start(widget, false, false, 0); // Remove the first pending message if (this.has_pending && message.type === Sms.MessageType.SENT) { this.pending_box.get_children()[0].destroy(); this.notify('has-pending'); } } /** * Message Entry */ // GtkEditable::changed _onEntryChanged(entry) { entry.secondary_icon_sensitive = (entry.text.length); } /** * Send the contents of the message entry to the address */ sendMessage(entry, signal_id, event) { // Don't send empty texts if (!this.message_entry.text.trim()) return; // Send the message this.device.activate_action( 'sendSms', new GLib.Variant('(ss)', [this.address, entry.text]) ); // Log the message as pending let message = new ConversationMessage({ body: entry.text, date: Date.now(), type: Sms.MessageType.SENT }); this.pending_box.add(message); this.notify('has-pending'); // Clear the entry this.message_entry.text = ''; } /** * Set the contents of the message entry * * @param {String} text - The message to place in the entry */ setMessage(text) { this.message_entry.text = text; this.message_entry.emit('move-cursor', 0, text.length, false); } }); /** * A Gtk.ApplicationWindow for SMS conversations */ var Window = GObject.registerClass({ GTypeName: 'GSConnectMessagingWindow', Properties: { 'address': GObject.ParamSpec.string( 'address', 'Address', 'The phone number of the active conversation', GObject.ParamFlags.READWRITE, '' ), 'device': GObject.ParamSpec.object( 'device', 'Device', 'The device associated with this window', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, GObject.Object ) }, Template: 'resource:///org/gnome/Shell/Extensions/GSConnect/messaging.ui', Children: [ 'headerbar', 'infobar', 'conversation-list', 'conversation-list-placeholder', 'conversation-stack' ] }, class Window extends Gtk.ApplicationWindow { _init(params) { this.connect_template(); super._init(params); this.headerbar.subtitle = this.device.name; this.settings = new Gio.Settings({ settings_schema: gsconnect.gschema.lookup('org.gnome.Shell.Extensions.GSConnect.Messaging', true), path: '/org/gnome/shell/extensions/gsconnect/messaging/' }); this.insert_action_group('device', this.device); // Device Status this.device.bind_property( 'connected', this.infobar, 'reveal-child', GObject.BindingFlags.INVERT_BOOLEAN ); this.restore_geometry(); // Contacts this.contact_list = new Contacts.ContactChooser({ device: this.device, store: this.device.contacts }); this.conversation_stack.add_named(this.contact_list, 'contact-list'); this._numberSelectedId = this.contact_list.connect( 'number-selected', this._onNumberSelected.bind(this) ); // Conversations this.conversation_list.set_sort_func(this._sortConversations); this._conversationsChangedId = this.sms.connect( 'notify::conversations', this._onConversationsChanged.bind(this) ); this._timestampConversationsId = GLib.timeout_add_seconds( GLib.PRIORITY_DEFAULT_IDLE, 60, this._timestampConversations.bind(this) ); // Conversations Placeholder this.conversation_list.set_placeholder(this.conversation_list_placeholder); // Cleanup on ::destroy this.connect('destroy', this._onDestroy); this._sync(); this._onConversationsChanged(); } vfunc_delete_event(event) { this.save_geometry(); return this.hide_on_delete(); } get address() { return this.conversation_stack.visible_child_name; } set address(address) { if (!address) { this.conversation_list.select_row(null); this.conversation_stack.set_visible_child_name('placeholder'); return; } // Get a contact object for this address let contact = this.device.contacts.query({number: address}); // Set the header bar title/subtitle this.headerbar.title = contact.name; this.headerbar.subtitle = address; // See if we have a nicer display number let number = address.toPhoneNumber(); for (let contactNumber of contact.numbers) { let cnumber = contactNumber.value.toPhoneNumber(); if (number.endsWith(cnumber) || cnumber.endsWith(number)) { this.headerbar.subtitle = contactNumber.value; break; } } // Create a conversation widget if there isn't one let conversation = this.getConversation(address); if (conversation === null) { conversation = new ConversationWidget({ device: this.device, address: address }); this.conversation_stack.add_named(conversation, address); } // Select the conversation and entry active this.conversation_stack.visible_child = conversation; this.conversation_stack.visible_child.message_entry.has_focus = true; // There was a pending message waiting for a contact to be chosen if (this._pendingShare) { conversation.setMessage(this._pendingShare); this._pendingShare = null; } } get sms() { if (!this._sms) { this._sms = this.device.lookup_plugin('sms'); } return this._sms; } _sync() { // Contacts let contacts = this.device.lookup_plugin('contacts'); if (contacts) { contacts.connected(); } else { this.device.contacts._loadFolks(); } // SMS history this.sms.connected(); } _onDestroy(window) { GLib.source_remove(window._timestampConversationsId); window.contact_list.disconnect(window._numberSelectedId); window.sms.disconnect(window._conversationsChangedId); window.disconnect_template(); } _onNewConversation() { this._sync(); this.conversation_stack.set_visible_child_name('contact-list'); this.conversation_list.select_row(null); this.contact_list.contact_entry.has_focus = true; } _onNumberSelected(list, number) { let summary = this.getSummary(number); this.conversation_list.select_row(summary); this.address = number; } /** * Conversations */ _onConversationsChanged() { let messages = new Map(); for (let thread of Object.values(this.sms.conversations)) { let message = thread[thread.length - 1]; messages.set(message.thread_id, message); } // Update existing summaries and destroy old ones for (let summary of this.conversation_list.get_children()) { let message = messages.get(summary.id); // If it's an existing conversation, update it if (message) { summary.message = message; messages.delete(summary.id); // Otherwise destroy it } else { // HACK: temporary mitigator for mysterious GtkListBox leak summary.run_dispose(); imports.system.gc(); // Also delete the conversation let conversation = this.getConversation(summary.message.address); if (conversation) { conversation.run_dispose(); imports.system.gc(); } } } // Add new summaries for (let message of messages.values()) { let contact = this.device.contacts.query({number: message.address}); let conversation = new ConversationSummary(contact, message); this.conversation_list.add(conversation); } } _onConversationSelected(box, row) { // Show the conversation for this number (if applicable) if (row) { this.address = row.message.address; // Show the placeholder } else { this.headerbar.title = _('Messaging'); this.headerbar.subtitle = this.device.name; } } _sortConversations(row1, row2) { return (row1.message.date > row2.message.date) ? -1 : 1; } _timestampConversations() { if (this.visible) { this.conversation_list.foreach(summary => summary.update()); } return GLib.SOURCE_CONTINUE; } /** * Find the conversation widget for an address * * @param {string} address - A contact address * @return {ConversationWidget|null} - The conversation widget or %null */ getConversation(address) { let number = address.toPhoneNumber(); for (let conversation of this.conversation_stack.get_children()) { // Skip contact list if (!conversation.address) continue; let cnumber = conversation.address.toPhoneNumber(); if (cnumber.endsWith(number) || number.endsWith(cnumber)) { return conversation; } } return null; } /** * Find the conversation summary row for an address * * @param {string} address - A contact address * @return {ConversationSummary|null} - The conversation summary or %null */ getSummary(address) { let number = address.toPhoneNumber(); for (let summary of this.conversation_list.get_children()) { let cnumber = summary.message.address.toPhoneNumber(); if (cnumber.endsWith(number) || number.endsWith(cnumber)) { return summary; } } 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} text - 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.conversation_stack.visible_child.setMessage(message); } } catch (e) { warning(e); } } }); /** * A Gtk.ApplicationWindow for selecting from open conversations */ var ConversationChooser = GObject.registerClass({ GTypeName: 'GSConnectConversationChooser', Properties: { 'device': GObject.ParamSpec.object( 'device', 'Device', 'The device associated with this window', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, GObject.Object ), 'message': GObject.ParamSpec.string( 'message', 'Message', 'The message to share', GObject.ParamFlags.READWRITE, '' ) } }, 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 let headerbar = new Gtk.HeaderBar({ title: _('Share Link'), subtitle: this.message, show_close_button: true, tooltip_text: this.message }); this.set_titlebar(headerbar); let 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)); headerbar.pack_start(newButton); // Conversations let scrolledWindow = new Gtk.ScrolledWindow({ can_focus: false, hexpand: true, vexpand: true, hscrollbar_policy: Gtk.PolicyType.NEVER }); this.add(scrolledWindow); this.conversation_list = new Gtk.ListBox({ activate_on_single_click: false }); this.conversation_list.set_sort_func(Window.prototype._sortConversations); this.conversation_list.connect('row-activated', this._select.bind(this)); scrolledWindow.add(this.conversation_list); // Filter Setup Window.prototype._populateConversations.call(this); this.show_all(); } get sms() { if (this._sms === undefined) { this._sms = this.device.lookup_plugin('sms'); } return this._sms; } _new(button) { let message = this.message; this.destroy(); this.sms.sms(); this.sms.window.address = null; this.sms.window._pendingShare = message; } _select(box, row) { this.sms.sms(); this.sms.window.address = row.message.address; this.sms.window.setMessage(this.message); this.destroy(); } }); gnome-shell-extension-gsconnect-20/src/service/ui/notification.js000066400000000000000000000065551341554142200254060ustar00rootroot00000000000000'use strict'; const Gio = imports.gi.Gio; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; var Dialog = GObject.registerClass({ GTypeName: 'GSConnectNotificationReplyDialog', Properties: { 'device': GObject.ParamSpec.object( 'device', 'Device', 'The device associated with this window', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, GObject.Object ) }, Template: 'resource:///org/gnome/Shell/Extensions/GSConnect/notification.ui', Children: [ 'infobar', 'notification-title', 'notification-body', 'message-entry' ] }, class Dialog extends Gtk.Dialog { _init(params) { this.connect_template(); super._init({ application: Gio.Application.get_default(), device: params.device, use_header_bar: true }); this.uuid = params.uuid; 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 let 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 = params.notification.text.linkify(); // Message Entry/Send Button this.device.bind_property( 'connected', this.message_entry, 'sensitive', GObject.BindingFlags.DEFAULT ); this._connectedId = this.device.connect( 'notify::connected', this._onStateChanged.bind(this) ); this._entryChangedId = this.message_entry.buffer.connect( 'changed', this._onStateChanged.bind(this) ); // Cleanup on ::destroy this.connect('destroy', this._onDestroy); } vfunc_response(response_id) { if (response_id === Gtk.ResponseType.OK) { // Refuse to send empty or whitespace only messages if (!this.message_entry.buffer.text.trim()) return; this.plugin.replyNotification( this.uuid, this.message_entry.buffer.text ); } this.destroy(); } get uuid() { return this._uuid; } set uuid(uuid) { // We must have a UUID if (uuid) { this._uuid = uuid; } else { this.destroy(); warning('no uuid for repliable notification'); } } get plugin() { if (!this._plugin) { this._plugin = this.device.lookup_plugin('notification'); } return this._plugin; } _onDestroy(window) { window.device.disconnect(window._connectedId); window.message_entry.buffer.disconnect(window._entryChangedId); window.disconnect_template(); } _onStateChanged() { switch (false) { case this.device.connected: case (this.message_entry.buffer.text.trim() !== ''): break; default: this.set_response_sensitive(Gtk.ResponseType.OK, true); } } }); gnome-shell-extension-gsconnect-20/src/service/ui/service.js000066400000000000000000000067301341554142200243530ustar00rootroot00000000000000'use strict'; const Gio = imports.gi.Gio; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; /** * A dialog for selecting a device */ var DeviceChooserDialog = GObject.registerClass({ GTypeName: 'GSConnectDeviceChooserDialog' }, class DeviceChooserDialog extends Gtk.Dialog { _init(params) { super._init({ use_header_bar: true, application: Gio.Application.get_default(), default_width: 300, default_height: 200, visible: true }); this.set_keep_above(true); // this._action = params.action; this._parameter = params.parameter; // HeaderBar let headerBar = this.get_header_bar(); headerBar.title = _('Select a Device'); headerBar.subtitle = params.title; headerBar.show_close_button = false; let selectButton = this.add_button(_('Select'), Gtk.ResponseType.OK); selectButton.sensitive = false; this.add_button(_('Cancel'), Gtk.ResponseType.CANCEL); this.set_default_response(Gtk.ResponseType.OK); // Device List let contentArea = this.get_content_area(); contentArea.border_width = 0; let scrolledWindow = new Gtk.ScrolledWindow({ border_width: 0, hexpand: true, vexpand: true, hscrollbar_policy: Gtk.PolicyType.NEVER, visible: true }); contentArea.add(scrolledWindow); this.list = new Gtk.ListBox({ activate_on_single_click: false, visible: true }); scrolledWindow.add(this.list); this.list.connect( 'row-activated', this._onDeviceActivated.bind(this) ); this.list.connect( 'selected-rows-changed', this._onDeviceSelected.bind(this) ); this._populate(); } vfunc_response(response_id) { if (response_id === Gtk.ResponseType.OK) { try { let device = this.list.get_selected_row().device; device.activate_action(this._action, this._parameter); } catch (e) { logError(e); } } this.destroy(); } _onDeviceActivated(box, row) { this.response(Gtk.ResponseType.OK); } _onDeviceSelected(box) { this.set_response_sensitive( Gtk.ResponseType.OK, (box.get_selected_row()) ); } _populate() { let devices = []; for (let device of this.application._devices.values()) { if (device.get_action_enabled(this._action)) { devices.push(device); } } for (let device of devices) { let row = new Gtk.ListBoxRow({visible: true}); this.list.add(row); row.device = device; let grid = new Gtk.Grid({ column_spacing: 6, margin: 6, visible: true }); row.add(grid); let icon = new Gtk.Image({ icon_name: device.icon_name, pixel_size: 32, visible: true }); grid.attach(icon, 0, 0, 1, 1); let name = new Gtk.Label({ label: device.name, halign: Gtk.Align.START, hexpand: true, visible: true }); grid.attach(name, 1, 0, 1, 1); } } }); gnome-shell-extension-gsconnect-20/src/service/ui/settings.js000066400000000000000000000501361341554142200245520ustar00rootroot00000000000000'use strict'; const GdkPixbuf = imports.gi.GdkPixbuf; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const Device = imports.service.ui.device; // Header for support logs const LOG_HEADER = new GLib.Bytes(` GSConnect Version: ${gsconnect.metadata.version} GSConnect Install: ${(gsconnect.is_local) ? 'user' : 'system'} GJS: ${imports.system.version} XDG_SESSION_TYPE: ${GLib.getenv('XDG_SESSION_TYPE')} GDMSESSION: ${GLib.getenv('GDMSESSION')} -------------------------------------------------------------------------------- `); /** * Generate a support log */ async function generateSupportLog(time) { try { let file = Gio.File.new_tmp('gsconnect.XXXXXX')[0]; let logFile = await new Promise((resolve, reject) => { file.replace_async(null, false, 2, 0, null, (file, res) => { try { resolve(file.replace_finish(res)); } catch (e) { reject(e); } }); }); 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??? let proc = new Gio.Subprocess({ flags: Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDOUT_PIPE, 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); } }); }); await new Promise((resolve, reject) => { Gio.AppInfo.launch_default_for_uri_async(file.get_uri(), null, null, (src, res) => { try { resolve(Gio.AppInfo.launch_default_for_uri_finish(res)); } catch (e) { reject(e); } }); }); } catch (e) { logError(e); } } /** * "Connect to..." Dialog */ var ConnectDialog = GObject.registerClass({ GTypeName: 'GSConnectConnectDialog', Properties: { 'has-devices': GObject.ParamSpec.boolean( 'has-devices', 'Has Devices', 'Whether any KDE Connect enabled bluetooth devices are present', GObject.ParamFlags.READABLE, false ) }, Template: 'resource:///org/gnome/Shell/Extensions/GSConnect/connect.ui', Children: [ 'cancel-button', 'connect-button', 'lan-radio', 'lan-grid', 'lan-ip', 'lan-port', 'bluez-radio', 'bluez-grid', 'bluez-device', 'bluez-devices' ] }, class ConnectDialog extends Gtk.Dialog { _init(parent = null) { super._init({ application: Gio.Application.get_default(), modal: (parent), transient_for: parent, use_header_bar: true }); // Bluez Device ComboBox let iconCell = new Gtk.CellRendererPixbuf({xpad: 6}); this.bluez_device.pack_start(iconCell, false); this.bluez_device.add_attribute(iconCell, 'pixbuf', 0); let nameCell = new Gtk.CellRendererText(); this.bluez_device.pack_start(nameCell, true); this.bluez_device.add_attribute(nameCell, 'text', 1); if (this.application.bluetooth) { this._devicesId = this.application.bluetooth.connect( 'notify::devices', this.populate.bind(this) ); this.populate(); } // Connection type selection this.lan_radio.bind_property( 'active', this.lan_grid, 'sensitive', GObject.BindingFlags.SYNC_CREATE ); this.bluez_radio.bind_property( 'active', this.bluez_grid, 'sensitive', GObject.BindingFlags.SYNC_CREATE ); // Hide Bluez and selection if there are no supported bluetooth devices this.bind_property( 'has-devices', this.bluez_radio, 'visible', GObject.BindingFlags.SYNC_CREATE ); this.bind_property( 'has-devices', this.bluez_grid, 'visible', GObject.BindingFlags.SYNC_CREATE ); this.bind_property( 'has-devices', this.lan_radio, 'visible', GObject.BindingFlags.SYNC_CREATE ); } vfunc_response(response_id) { if (response_id === Gtk.ResponseType.OK) { try { let address; // Bluetooth device selected if (this.bluez_device.visible && this.bluez_radio.active) { address = this.bluez_device.active_id; // Lan host/port entered } else if (this.lan_ip.text) { address = Gio.InetSocketAddress.new_from_string( this.lan_ip.text, this.lan_port.value ); } else { return false; } this.application.broadcast(address); } catch (e) { logError(e); } } if (this._deviceId) { this.application.bluetooth.disconnect(this._devicesId); } this.destroy(); return false; } get has_devices() { if (!this.application.bluetooth) { return false; } return this.application.bluetooth.devices.length > 0; } populate() { this.bluez_devices.clear(); let theme = Gtk.IconTheme.get_default(); if (this.has_devices) { for (let device of this.application.bluetooth.devices) { let pixbuf = theme.load_icon( device.Icon, 16, Gtk.IconLookupFlags.FORCE_SIZE ); this.bluez_devices.set( this.bluez_devices.append(), [0, 1, 2], [pixbuf, `${device.Alias} (${device.Adapter})`, device.g_object_path] ); } this.bluez_device.active_id = this.application.bluetooth.devices[0].g_object_path; } this.notify('has-devices'); } }); /** * A Device Row */ const DeviceRow = GObject.registerClass({ GTypeName: 'GSConnectDeviceRow', Properties: { 'connected': GObject.ParamSpec.boolean( 'connected', 'deviceConnected', 'Whether the device is connected', GObject.ParamFlags.READWRITE, false ), 'paired': GObject.ParamSpec.boolean( 'paired', 'devicePaired', 'Whether the device is paired', GObject.ParamFlags.READWRITE, false ), 'status': GObject.ParamSpec.string( 'status', 'Device Status', 'The status of the device', GObject.ParamFlags.READWRITE, null ) } }, class GSConnectDeviceRow extends Gtk.ListBoxRow { _init(device) { super._init({ height_request: 52, selectable: false }); this.set_name(device.id); this.device = device; let grid = new Gtk.Grid({ column_spacing: 12, margin_left: 20, // 20 margin_right: 20, margin_bottom: 8, // 16 margin_top: 8 }); this.add(grid); let icon = new Gtk.Image({ gicon: new Gio.ThemedIcon({name: `${this.device.icon_name}-symbolic`}), icon_size: Gtk.IconSize.BUTTON }); grid.attach(icon, 0, 0, 1, 1); let title = new Gtk.Label({ halign: Gtk.Align.START, hexpand: true, valign: Gtk.Align.CENTER, vexpand: true }); device.settings.bind('name', title, 'label', 0); grid.attach(title, 1, 0, 1, 1); let status = new Gtk.Label({ halign: Gtk.Align.END, hexpand: true, valign: Gtk.Align.CENTER, vexpand: true }); this.bind_property('status', status, 'label', 2); grid.attach(status, 2, 0, 1, 1); this.connect('notify::connected', () => this.notify('status')); device.bind_property('connected', this, 'connected', 2); this.connect('notify::paired', () => this.notify('status')); device.bind_property('paired', this, 'paired', 2); this.show_all(); } get status() { if (!this.paired) { return _('Unpaired'); } else if (!this.connected) { return _('Disconnected'); } return _('Connected'); } }); var Window = GObject.registerClass({ GTypeName: 'GSConnectSettingsWindow', 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/settings.ui', Children: [ // HeaderBar 'headerbar', 'infobar', 'stack', 'service-menu', 'service-refresh', 'service-edit', 'service-entry', 'device-menu', 'prev-button', 'service-window', 'service-box', 'info-label', // Device List 'device-list', 'device-list-spinner', 'device-list-placeholder' ] }, class SettingsWindow extends Gtk.ApplicationWindow { _init(params) { this.connect_template(); super._init({ application: Gio.Application.get_default(), title: _('Settings') }); this.settings = new Gio.Settings({ settings_schema: gsconnect.gschema.lookup( 'org.gnome.Shell.Extensions.GSConnect.Preferences', true ), path: '/org/gnome/shell/extensions/gsconnect/preferences/' }); gsconnect.settings.bind( 'discoverable', this.infobar, 'reveal-child', Gio.SettingsBindFlags.INVERT_BOOLEAN ); // HeaderBar (Service Name) this.headerbar.title = gsconnect.settings.get_string('public-name'); this.service_entry.text = this.headerbar.title; // Scroll with keyboard focus this.service_box.set_focus_vadjustment(this.service_window.vadjustment); // Downloads link; Account for some corner cases with a fallback let download_dir = GLib.get_user_special_dir( GLib.UserDirectory.DIRECTORY_DOWNLOAD ); if (!download_dir || download_dir === GLib.get_home_dir()) { download_dir = GLib.build_filenamev([GLib.get_home_dir(), 'Downloads']); } // TRANSLATORS: Description of where directly shared files are stored. this.info_label.label = _('Transferred files are placed in the Downloads folder.').format( 'file://' + download_dir ); // let displayMode = new Gio.PropertyAction({ name: 'display-mode', property_name: 'display-mode', object: this }); this.add_action(displayMode); // About Dialog let aboutDialog = new Gio.SimpleAction({name: 'about'}); aboutDialog.connect('activate', this._aboutDialog.bind(this)); this.add_action(aboutDialog); // "Connect to..." Dialog let connectDialog = new Gio.SimpleAction({name: 'connect'}); connectDialog.connect('activate', this._connectDialog); this.add_action(connectDialog); // "Generate Support Log" GAction let generateSupportLog = new Gio.SimpleAction({name: 'support-log'}); generateSupportLog.connect('activate', this._generateSupportLog); this.add_action(generateSupportLog); // App Menu (in-window only) this.service_menu.set_menu_model( this.application.get_menu_by_id('service-menu') ); // Device List this.device_list.set_header_func((row, before) => { if (before) row.set_header(new Gtk.Separator({visible: true})); }); this.device_list.set_placeholder(this.device_list_placeholder); // Setup devices this._devicesChangedId = gsconnect.settings.connect( 'changed::devices', this._onDevicesChanged.bind(this) ); this._onDevicesChanged(); // If there are no devices, it's safe to auto-broadcast this._refresh(); GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 5, this._refresh.bind(this)); // Restore window size/maximized/position this.restore_geometry(); } get display_mode() { if (gsconnect.settings.get_boolean('show-indicators')) { return 'panel'; } else { return 'user-menu'; } } set display_mode(mode) { gsconnect.settings.set_boolean('show-indicators', (mode === 'panel')); } vfunc_delete_event(event) { this.save_geometry(); return this.hide_on_delete(); } _refresh() { if (this.application.devices.length < 1 && this.stack.visible_child_name === 'service') { this.application.broadcast(); this.device_list_spinner.active = true; } else { this.device_list_spinner.active = false; } return GLib.SOURCE_CONTINUE; } /** * 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( gsconnect.app_path + '/icons/' + gsconnect.app_id + '.svg', 128, 128, true ), program_name: 'GSConnect', // TRANSLATORS: eg. 'Translator Name ' translator_credits: _('translator-credits'), version: `${gsconnect.metadata.version}`, website: gsconnect.metadata.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(Gio.Application.get_default()._window); } /** * "Generate Support Log" GAction */ _generateSupportLog() { let 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 gsconnect.settings.set_boolean('debug', true); let now = GLib.DateTime.new_now_local().format('%R'); dialog.connect('response', (dialog, response_id) => { // Disable debug logging and destroy the dialog gsconnect.settings.set_boolean('debug', false); dialog.destroy(); // Only generate a log if instructed if (response_id === Gtk.ResponseType.OK) { generateSupportLog(now); } }); dialog.show_all(); } /** * HeaderBar Callbacks */ _onPrevious(button, event) { // HeaderBar (Service) this.prev_button.visible = false; this.device_menu.visible = false; this.service_refresh.visible = true; this.service_edit.visible = true; this.service_menu.visible = true; this.headerbar.title = gsconnect.settings.get_string('public-name'); this.headerbar.subtitle = null; // Panel this.stack.visible_child_name = 'service'; this._setDeviceMenu(); } _onEditServiceName(button, event) { this.service_entry.text = this.headerbar.title; } _onUnfocusServiceName(entry, event) { this.service_edit.active = false; return false; } _onSetServiceName(button, event) { if (this.service_entry.text.length) { this.headerbar.title = this.service_entry.text; gsconnect.settings.set_string('public-name', this.service_entry.text); } this.service_edit.active = false; } /** * Context Switcher */ _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) { this.device_menu.insert_action_group('device', panel.device); this.device_menu.insert_action_group('settings', panel.actions); this.device_menu.set_menu_model(panel.menu); } } _onDeviceSelected(box, row) { let name = (typeof box === 'string') ? box : row.get_name(); // Transition the panel let panel = this.stack.get_child_by_name(name); this.stack.visible_child_name = name; this._setDeviceMenu(this.stack.visible_child); // HeaderBar (Device) this.service_refresh.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 = panel.device.name; this.headerbar.subtitle = panel.device.display_type; } _onDevicesChanged() { try { for (let id of this.application.devices) { if (!this.stack.get_child_by_name(id)) { this.addDevice(id); } } this.stack.foreach(panel => { let id = panel.get_name(); if (id !== 'service' && !this.application.devices.includes(id)) { if (this.stack.visible_child === panel) { this._onPrevious(); } panel._destroy(); panel.destroy(); } }); this.device_list.foreach(row => { if (!this.application.devices.includes(row.get_name())) { // HACK: temporary mitigator for mysterious GtkListBox leak //row.destroy(); row.run_dispose(); imports.system.gc(); } }); } catch (e) { logError(e); } } addDevice(id) { let device = this.application._devices.get(id); // Add the device settings to the content stack this.stack.add_titled(new Device.DevicePreferences(device), id, device.name); this.device_list.add(new DeviceRow(device)); } }); gnome-shell-extension-gsconnect-20/src/service/ui/telephony.js000066400000000000000000000123651341554142200247230ustar00rootroot00000000000000'use strict'; const Gio = imports.gi.Gio; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const Contacts = imports.service.ui.contacts; const Messaging = imports.service.ui.messaging; var Dialog = GObject.registerClass({ GTypeName: 'GSConnectLegacyMessagingDialog', Properties: { 'device': GObject.ParamSpec.object( 'device', 'Device', 'The device associated with this window', GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, GObject.Object ) }, Template: 'resource:///org/gnome/Shell/Extensions/GSConnect/telephony.ui', Children: [ 'infobar', 'stack', 'message', 'message-box', 'message-entry' ] }, class Dialog extends Gtk.Dialog { _init(params) { this.connect_template(); super._init({ application: Gio.Application.get_default(), device: params.device, use_header_bar: true, visible: true }); this.set_response_sensitive(Gtk.ResponseType.OK, false); // 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.message_entry, 'sensitive', GObject.BindingFlags.DEFAULT ); this._connectedId = this.device.connect( 'notify::connected', this._onStateChanged.bind(this) ); this._entryChangedId = this.message_entry.buffer.connect( 'changed', this._onStateChanged.bind(this) ); // Set the message if given if (params.message) { this.message = params.message; let message = new Messaging.ConversationMessage(this.message); message.margin_bottom = 12; this.message_box.add(message); } // Set the address if given if (params.address) { this.address = params.address; // Otherwise load the contact list } else { this.contact_list = new Contacts.ContactChooser({ device: this.device, store: this.device.contacts }); this.stack.add_named(this.contact_list, 'contact-list'); this.stack.child_set_property(this.contact_list, 'position', 0); this._numberSelectedId = this.contact_list.connect( 'number-selected', this._onNumberSelected.bind(this) ); this.stack.visible_child_name = 'contact-list'; } // Cleanup on ::destroy this.connect('destroy', this._onDestroy); } vfunc_response(response_id) { if (response_id === Gtk.ResponseType.OK) { // Refuse to send empty or whitespace only texts if (!this.message_entry.buffer.text.trim()) return; this.sms.sendSms(this.address, this.message_entry.buffer.text); } this.destroy(); } get address() { return this._address || null; } set address(value) { if (!value) return; this._address = value; let headerbar = this.get_titlebar(); headerbar.title = this.contact.name; headerbar.subtitle = value; // See if we have a nicer display number let number = value.toPhoneNumber(); for (let contactNumber of this.contact.numbers) { let cnumber = contactNumber.value.toPhoneNumber(); if (number.endsWith(cnumber) || cnumber.endsWith(number)) { headerbar.subtitle = contactNumber.value; break; } } this.stack.visible_child_name = 'message'; this._onStateChanged(); } get contact() { // Ensure we have a contact and hold a reference to it if (!this._contact) { this._contact = this.device.contacts.query({number: this.address}); } return this._contact; } get sms() { if (!this._sms) { this._sms = this.device.lookup_plugin('sms'); } return this._sms; } _onDestroy(window) { if (window._numberSelectedId) { window.contact_list.disconnect(window._numberSelectedId); } window.device.disconnect(window._connectedId); window.message_entry.buffer.disconnect(window._entryChangedId); window.disconnect_template(); } _onNumberSelected(list, number) { this.address = number; } _onStateChanged() { switch (false) { case this.device.connected: case (this.message_entry.buffer.text.trim() !== ''): case (this.stack.visible_child_name === 'message'): this.set_response_sensitive(Gtk.ResponseType.OK, false); break; default: this.set_response_sensitive(Gtk.ResponseType.OK, true); } } /** * Set the contents of the message entry * * @param {String} text - The message to place in the entry */ setMessage(text) { this.message_entry.buffer.text = text; } }); gnome-shell-extension-gsconnect-20/src/shell/000077500000000000000000000000001341554142200214015ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/src/shell/device.js000066400000000000000000000156411341554142200232050ustar00rootroot00000000000000'use strict'; const Clutter = imports.gi.Clutter; const Gio = imports.gi.Gio; const GObject = imports.gi.GObject; const St = imports.gi.St; const PanelMenu = imports.ui.panelMenu; const PopupMenu = imports.ui.popupMenu; const _ = gsconnect._; const GMenu = imports.shell.gmenu; const Tooltip = imports.shell.tooltip; /** * A battery widget with an icon, text percentage and time estimate tooltip */ var 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' }); this.add_child(this.icon); // Battery Estimate this.tooltip = new Tooltip.Tooltip({ parent: this, text: this.battery_label }); // 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'); // Refresh when mapped this._mappedId = this.connect('notify::mapped', this._sync.bind(this)); // Cleanup this.connect('destroy', this._onDestroy); } _onActionChanged(action_group, action_name) { if (action_name === 'battery') { if (action_group.has_action('battery')) { let value = action_group.get_action_state('battery'); let [charging, icon_name, level, time] = value.deep_unpack(); this.battery = { Charging: charging, IconName: icon_name, Level: level, Time: time }; } else { this.battery = null; } this._sync(); } } _onStateChanged(action_group, action_name, value) { if (action_name === 'battery') { let [charging, icon_name, level, time] = value.deep_unpack(); this.battery = { Charging: charging, IconName: icon_name, Level: level, Time: time }; } } get battery_label() { if (!this.battery) return null; let {Charging, Level, Time} = this.battery; if (Level === 100) { // TRANSLATORS: When the battery level is 100% return _('Fully Charged'); } else if (Time === 0) { // TRANSLATORS: When no time estimate for the battery is available // EXAMPLE: 42% (Estimating…) return _('%d%% (Estimating…)').format(Level); } Time = Time / 60; let minutes = Math.floor(Time % 60); let hours = Math.floor(Time / 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); actor.disconnect(actor._mappedId); } _sync() { this.visible = (this.battery); if (this.visible && this.mapped) { this.icon.icon_name = this.battery.IconName; this.label.text = (this.battery.Level > -1) ? `${this.battery.Level}%` : ''; this.tooltip.text = this.battery_label; } } }); /** * A PopupMenu used as an information and control center for a device */ var Menu = class Menu extends PopupMenu.PopupMenuSection { _init(params) { super._init(); 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._nameId = this.device.settings.connect( 'changed::name', this._onNameChanged.bind(this) ); this.actor.connect('destroy', this._onDestroy); // Title -> Battery this._battery = new Battery({device: this.device}); this._title.actor.add_child(this._battery); // Actions if (this.menu_type === 'icon') { this._actions = new GMenu.IconBox({ action_group: this.device.action_group, menu_model: this.device.menu_model }); } else if (this.menu_type === 'list') { this._actions = new GMenu.ListBox({ action_group: this.device.action_group, menu_model: this.device.menu_model }); } this.addMenuItem(this._actions); } _onDestroy(actor) { actor._delegate.device.settings.disconnect(actor._delegate._nameId); } _onNameChanged(settings) { this._title.label.text = settings.get_string('name'); } isEmpty() { return false; } }; /** * An indicator representing a Device in the Status Area */ var Indicator = class Indicator extends PanelMenu.Button { _init(params) { super._init(null, `${params.device.Name} Indicator`, false); Object.assign(this, params); // Device Icon let icon = new St.Icon({ gicon: new Gio.ThemedIcon({name: this.device.IconName}), style_class: 'system-status-icon gsconnect-device-indicator' }); this.actor.add_child(icon); // Menu let menu = new Menu({ device: this.device, menu_type: 'icon' }); this.menu.addMenuItem(menu); } }; gnome-shell-extension-gsconnect-20/src/shell/donotdisturb.js000066400000000000000000000202051341554142200244560ustar00rootroot00000000000000'use strict'; const Clutter = imports.gi.Clutter; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const St = imports.gi.St; const ModalDialog = imports.ui.modalDialog; const PopupMenu = imports.ui.popupMenu; const Util = imports.misc.util; const _ = gsconnect._; const Tooltip = imports.shell.tooltip; /** * A simple implementation of GtkRadioButton for St */ var RadioButton = GObject.registerClass({ GTypeName: 'GSConnectShellRadioButton' }, class RadioButton extends St.BoxLayout { _init(params) { params = Object.assign({ text: null, widget: null, group: [], active: false }, params); super._init({ style_class: 'gsconnect-radio-button', vertical: false }); this.button = new St.Button({ style_class: 'pager-button', child: new St.Icon({icon_name: 'radio-symbolic', icon_size: 16}) }); this.add_child(this.button); this.add_child(new St.Label()); if (params.text) { this.text = params.text; } else { this.widget = params.widget; } // this.button.connect('clicked', () => { this.active = true; }); // Group this.group = params.group; this.connect('destroy', () => { this.group.splice(this.group.indexOf(this), 1); }); this.active = params.active; } get active() { return (this.button.child.icon_name === 'radio-checked-symbolic'); } set active(bool) { if (bool) { this.button.child.icon_name = 'radio-checked-symbolic'; for (let radio of this.group) { if (radio !== this) { radio.button.child.icon_name = 'radio-symbolic'; } } } else { this.button.child.icon_name = 'radio-symbolic'; } } get group() { return this._group; } set group(group) { this._group = group; if (this._group.indexOf(this) < 0) { this._group.push(this); } this.active = (this.group.length === 1); } get text() { if (this.widget instanceof St.Label) { return this.widget.text; } return null; } set text(text) { if (typeof text === 'string') { this.widget = new St.Label({text: text}); } } get widget () { return this.get_child_at_index(1); } set widget (widget) { if (widget instanceof Clutter.Actor) { widget.y_align = Clutter.ActorAlign.CENTER; this.replace_child(this.widget, widget); } } }); var Dialog = class Dialog extends ModalDialog.ModalDialog { _init() { super._init({styleClass: 'gsconnect-dnd-dialog'}); this.contentLayout.style_class = 'nm-dialog-content'; // Header let headerBox = new St.BoxLayout({ style_class: 'nm-dialog-header-hbox' }); this.contentLayout.add(headerBox); let icon = new St.Icon({ style_class: 'nm-dialog-header-icon', gicon: new Gio.ThemedIcon({ name: 'preferences-system-time-symbolic' }) }); headerBox.add(icon); let titleBox = new St.BoxLayout({vertical: true}); headerBox.add(titleBox); let header = new St.Label({ style_class: 'nm-dialog-header', text: _('Do Not Disturb') }); titleBox.add(header); let subheader = new St.Label({ style_class: 'nm-dialog-subheader', text: _('Silence Mobile Device Notifications') }); titleBox.add(subheader); // Content let radioList = new St.BoxLayout({ style_class: 'gsconnect-radio-list', vertical: true }); this.contentLayout.add(radioList); // 1 hour in seconds this._time = 1 * 60 * 60; this._radioIndefinite = new RadioButton({ text: _('Until you turn off Do Not Disturb') }); radioList.add(this._radioIndefinite); // Duration Timer let timer = new St.BoxLayout({ vertical: false, x_expand: true, style_class: 'gsconnect-dnd-timer' }); let now = GLib.DateTime.new_now_local(); this.timerLabel = new St.Label({ text: _('Until %s (%s)').format( Util.formatTime(now.add_seconds(this._time)), this._getDurationLabel() ), y_align: Clutter.ActorAlign.CENTER }); timer.add_child(this.timerLabel); this._timerRemove = new St.Button({ style_class: 'pager-button', child: new St.Icon({icon_name: 'list-remove-symbolic'}) }); this._timerRemove.connect('clicked', this._removeTime.bind(this)); timer.add_child(this._timerRemove); this._timerAdd = new St.Button({ style_class: 'pager-button', child: new St.Icon({icon_name: 'list-add-symbolic'}) }); this._timerAdd.connect('clicked', this._addTime.bind(this)); timer.add_child(this._timerAdd); this._radioDuration = new RadioButton({ widget: timer, group: this._radioIndefinite.group, active: true }); radioList.add(this._radioDuration); // Dialog Buttons this.setButtons([ {label: _('Cancel'), action: this._cancel.bind(this), default: true}, {label: _('Done'), action: this._done.bind(this)} ]); } _cancel() { gsconnect.settings.reset('donotdisturb'); this.close(); } _done() { let time; if (this._radioDuration.active) { let now = GLib.DateTime.new_now_local(); time = now.add_seconds(this._time).to_unix(); } else { time = GLib.MAXINT32; } gsconnect.settings.set_int('donotdisturb', time); this.close(); } _addTime() { if (this._time < 60 * 60) { this._time += 15 * 60; } else { this._time += 60 * 60; } this._setTimeLabel(); } _removeTime() { if (this._time <= 60 * 60) { this._time -= 15 * 60; } else { this._time -= 60 * 60; } this._setTimeLabel(); } _getDurationLabel() { if (this._time >= 60 * 60) { let hours = this._time / 3600; // TRANSLATORS: Time duration in hours (eg. 2 hours) return gsconnect.ngettext('%d hour', '%d hours', hours).format(hours); } else { // TRANSLATORS: Time duration in minutes (eg. 15 minutes) return gsconnect.ngettext('%d minute', '%d minutes', (this._time / 60)).format(this._time / 60); } } _setTimeLabel() { this._timerRemove.reactive = (this._time > 15 * 60); this._timerAdd.reactive = (this._time < 12 * 60 * 60); let now = GLib.DateTime.new_now_local(); // TRANSLATORS: Time until change with time duration // EXAMPLE: Until 10:00 (2 hours) this.timerLabel.text = _('Until %s (%s)').format( Util.formatTime(now.add_seconds(this._time)), this._getDurationLabel() ); } }; var MenuItem = class MenuItem extends PopupMenu.PopupSwitchMenuItem { _init() { super._init(_('Do Not Disturb'), false); // Update the toggle state when 'paintable' this.actor.connect('notify::mapped', () => { let now = GLib.DateTime.new_now_local().to_unix(); this.setToggleState(gsconnect.settings.get_int('donotdisturb') > now); }); this.connect('toggled', (item) => { // The state has already been changed when this is emitted if (item.state) { let dialog = new Dialog(); dialog.open(); } else { gsconnect.settings.reset('donotdisturb'); } item._getTopMenu().close(true); }); } }; gnome-shell-extension-gsconnect-20/src/shell/gmenu.js000066400000000000000000000411021341554142200230500ustar00rootroot00000000000000'use strict'; const Atk = imports.gi.Atk; const Clutter = imports.gi.Clutter; const Gio = imports.gi.Gio; const GObject = imports.gi.GObject; const St = imports.gi.St; const PopupMenu = imports.ui.popupMenu; const Tooltip = imports.shell.tooltip; /** * Get a dictionary of a GMenuItem's attributes * * @param {Gio.MenuModel} model - The menu model containing the item * @param {number} index - The index of the item in @model * @return {object} - A dictionary of the item's attributes */ function getItemInfo(model, index) { let info = { target: null, links: [] }; // let iter = model.iterate_item_attributes(index); while (iter.next()) { let name = iter.get_name(); let value = iter.get_value(); switch (name) { case 'icon': info[name] = Gio.Icon.deserialize(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; } /** * */ var ListBox = class ListBox extends PopupMenu.PopupMenuSection { _init(params) { super._init(); Object.assign(this, params); // Main Actor this.actor = new St.BoxLayout({ x_expand: 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.connect('transitions-completed', this._onTransitionsCompleted); this.actor.add_child(this.box); // Submenu Container this.sub = new St.BoxLayout({ clip_to_allocation: true, style_class: 'popup-sub-menu', vertical: false, visible: false, x_expand: true }); this.sub.connect('key-press-event', this._onSubmenuCloseKey); this.sub.connect('transitions-completed', this._onTransitionsCompleted); this.sub._delegate = this; this.actor.add_child(this.sub); // Refresh the menu when mapped let _mappedId = this.actor.connect( 'notify::mapped', this._onMapped.bind(this) ); this.actor.connect('destroy', (actor) => actor.disconnect(_mappedId)); // Watch the model for changes let _menuId = this.menu_model.connect( 'items-changed', this._onItemsChanged.bind(this) ); this.connect('destroy', (menu) => menu.menu_model.disconnect(_menuId)); this._onItemsChanged(); } _onMapped(actor) { if (actor.mapped) { this._onItemsChanged(); // We use this instead of close() to avoid touching finalized objects } else { this.box.show(); this.box.set_width(-1); this._submenu = null; this.sub.hide(); this.sub.set_width(-1); this.sub.get_children().map(menu => menu.hide()); } } _onSubmenuCloseKey(actor, event) { let menu = actor.get_parent()._delegate; if (menu.submenu && event.get_key_symbol() == Clutter.KEY_Left) { menu.submenu.submenu_for.setActive(true); menu.submenu = null; return Clutter.EVENT_STOP; } return Clutter.EVENT_PROPAGATE; } _onSubmenuOpenKey(actor, event) { let 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; } _addGMenuItem(info) { let action_name = info.action.split('.')[1]; let action_target = info.target; // TODO: Use an image menu item if there's an icon? let item = new PopupMenu.PopupMenuItem(info.label); item.actor.visible = this.action_group.get_action_enabled(action_name); this.addMenuItem(item); // Modify the ::activate callback to invoke the GAction or submenu item.disconnect(item._activateId); item._activateId = item.connect('activate', (item, event) => { this.emit('activate', item); if (item.submenu) { this.submenu = item.submenu; } else { this.action_group.activate_action(action_name, action_target); this.itemActivated(); } }); return item; } _addGMenuSection(model) { let section = new ListBox({ menu_model: model, action_group: this.action_group }); this.addMenuItem(section); } _addGMenuSubmenu(model, item) { // Add an expander arrow to the item let 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({ menu_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()); let len = this.menu_model.get_n_items(); for (let i = 0; i < len; i++) { let info = getItemInfo(this.menu_model, i); let item; // A regular item if (info.hasOwnProperty('label')) { item = this._addGMenuItem(info); } for (let 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, prepend a "go back" item if (this.submenu_for) { let prev = new PopupMenu.PopupMenuItem(this.submenu_for.label.text); this.addMenuItem(prev, 0); // Make the title bold and replace the ornament with an arrow prev.label.style = 'font-weight: bold;'; prev._ornamentLabel.text = '\u25C2'; // Modify the ::activate callback to close the submenu prev.disconnect(prev._activateId); prev._activateId = prev.connect('activate', (item, event) => { this.emit('activate', item); this._parent.submenu = null; }); } } _onTransitionsCompleted(actor) { let menu = actor.get_parent()._delegate; if (menu.submenu) { menu.box.hide(); menu.box.set_width(0); } else { menu.sub.hide(); menu.sub.set_width(0); menu.sub.get_children().map(menu => menu.hide()); } } get submenu() { return this._submenu || null; } set submenu(submenu) { let allocWidth = this.actor.allocation.x2 - this.actor.allocation.x1; // 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_width(allocWidth); this.sub.show(); this.box.set_width(0); } else { this.box.set_width(allocWidth); this.box.show(); this.sub.set_width(0); } // Reset the animation this.box.restore_easing_state(); this.sub.restore_easing_state(); // this._submenu = submenu; } }; /** * A St.Button subclass for iconic GMenu items */ var IconButton = GObject.registerClass({ GTypeName: 'GSConnectShellIconButton' }, class Button extends St.Button { _init(params) { super._init({ style_class: 'system-menu-action 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.Tooltip({ parent: this, markup: params.info.label }); } if (params.info.hasOwnProperty('icon')) { this.child = new St.Icon({gicon: params.info.icon}); } // Submenu for (let 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({ menu_model: link.value, action_group: this.action_group }); this.submenu.actor.style_class = 'popup-sub-menu'; this.submenu.actor.visible = false; } } this.connect('clicked', this._onClicked); } // 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 _onClicked(button, clicked_button) { // Unless this has submenu activate the action and close if (!button.toggle_mode) { button._parent._getTopMenu().close(); button.action_group.activate_action( button.action_name, button.action_target ); // StButton.checked has already been toggled so we're opening } else if (button.checked) { button._parent.submenu = button.submenu; // If this is the active submenu being closed, animate-close it } else if (button._parent.submenu === button.submenu) { button._parent.submenu = null; } } }); var IconBox = class IconBox extends PopupMenu.PopupMenuSection { _init(params) { super._init(); Object.assign(this, params); // Main Actor this.actor = new St.BoxLayout({ vertical: true, x_expand: true }); this.actor.connect('notify::mapped', this._onMapped); this.actor._delegate = this; // Button Box this.box._delegate = this; this.box.style_class = 'gsconnect-icon-box'; this.box.vertical = false; this.actor.add_child(this.box); // Submenu Container this.sub = new St.BoxLayout({ clip_to_allocation: true, style_class: 'popup-sub-menu', vertical: true, x_expand: true }); this.sub.connect('transitions-completed', this._onTransitionsCompleted); this.sub._delegate = this; this.actor.add_child(this.sub); // Track menu items so we can use ::items-changed this._menu_items = new Map(); // GMenu let _itemsChangedId = this.menu_model.connect( 'items-changed', this._onItemsChanged.bind(this) ); // GActions let _actionAddedId = this.action_group.connect( 'action-added', this._onActionChanged.bind(this) ); let _actionEnabledChangedId = this.action_group.connect( 'action-enabled-changed', this._onActionChanged.bind(this) ); let _actionRemovedId = this.action_group.connect( 'action-removed', this._onActionChanged.bind(this) ); this.connect('destroy', (actor) => { actor.menu_model.disconnect(_itemsChangedId); actor.action_group.disconnect(_actionAddedId); actor.action_group.disconnect(_actionEnabledChangedId); actor.action_group.disconnect(_actionRemovedId); }); } get submenu() { return this._submenu || null; } set submenu(submenu) { if (submenu) { this.box.get_children().map(button => { 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 ? -1 : 0); this.sub.restore_easing_state(); this._submenu = submenu; } _onActionChanged(group, name, enabled) { let 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) { let button = this.box.get_child_at_index(position); (button.submenu) ? button.submenu.destroy() : null; button.destroy(); this._menu_items.delete(button.action_name); removed--; } // Add items for (let i = 0; i < added; i++) { let index = position + i; // Create an iconic button let button = new IconButton({ action_group: this.action_group, info: getItemInfo(model, index), // TODO: 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 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 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); } } _onMapped(actor) { // Close everything down manually when unmapped if (!actor.mapped) { let menu = actor._delegate; menu._submenu = null; menu.box.get_children().map(button => button.checked = false); menu.sub.get_children().map(submenu => submenu.hide()); } } _onTransitionsCompleted(actor) { let menu = actor._delegate; menu.box.get_children().map(button => { if (button.submenu && button.submenu !== menu.submenu) { button.checked = false; button.submenu.actor.hide(); } }); } // PopupMenu.PopupMenuBase overrides isEmpty() { return (this.box.get_children().length === 0); } _setParent(parent) { super._setParent(parent); this._onItemsChanged(this.menu_model, 0, 0, this.menu_model.get_n_items()); } }; gnome-shell-extension-gsconnect-20/src/shell/keybindings.js000066400000000000000000000057721341554142200242600ustar00rootroot00000000000000'use strict'; const Main = imports.ui.main; const Meta = imports.gi.Meta; const Shell = imports.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 */ var Manager = class Manager { constructor() { this._keybindings = new Map(); this._acceleratorActivatedId = global.display.connect( 'accelerator-activated', this._onAcceleratorActivated.bind(this) ); } _onAcceleratorActivated(display, action, deviceId, timestamp) { try { let 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 'q' * @param {Function} callback - A callback for the accelerator * @return {Number} - A non-zero action id on success, or 0 on failure */ add(accelerator, callback) { let action = global.display.grab_accelerator(accelerator); if (action !== Meta.KeyBindingAction.NONE) { let name = Meta.external_binding_name_for_action(action); Main.wm.allowKeybinding(name, Shell.ActionMode.ALL); this._keybindings.set(action, {name: name, callback: callback}); } else { logError(new Error(`Failed to add keybinding: '${accelerator}'`)); } return action; } /** * Remove a keybinding * * @param {Number} accelerator - A non-zero action id returned by add() */ remove(action) { try { let 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 (let action of this._keybindings.keys()) { this.remove(action); } } /** * Destroy the keybinding manager and remove all keybindings */ destroy() { global.display.disconnect(this._acceleratorActivatedId); this.removeAll(); } }; gnome-shell-extension-gsconnect-20/src/shell/notification.js000066400000000000000000000324371341554142200244360ustar00rootroot00000000000000'use strict'; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const St = imports.gi.St; const Main = imports.ui.main; const MessageTray = imports.ui.messageTray; const NotificationDaemon = imports.ui.notificationDaemon; const _ = gsconnect._; // deviceId Pattern (|) const DEVICE_REGEX = /^([^|]+)\|(.+)$/; // requestReplyId Pattern (|)|) const REPLY_REGEX = /^([^|]+)\|(.+)\|([0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})$/i; /** * A slightly modified Notification Banner with an entry field */ class RepliableNotificationBanner extends MessageTray.NotificationBanner { _init(notification) { super._init(notification); // Ensure there's an action area if (!this._buttonBox) { this._buttonBox = new St.BoxLayout({ style_class: 'notification-actions', x_expand: true }); this.setActionArea(this._buttonBox); global.focus_manager.add_group(this._buttonBox); } // Reply Button let button = new St.Button({ style_class: 'notification-button', label: _('Reply'), x_expand: true, can_focus: true }); this._buttonBox.add_child(button); // Reply Entry let entry = new St.Entry({ can_focus: true, hint_text: _('Type a message'), style_class: 'chat-response', x_expand: true, visible: false }); this._buttonBox.add_child(entry); // Enter to send // TODO: secondary-icon entry.clutter_text.connect( 'activate', this._onEntryActivated.bind(this) ); // Swap the button out for the entry button.connect('clicked', (button) => { this.focused = true; entry.visible = true; entry.clutter_text.grab_key_focus(); button.visible = false; }); // Make sure we release the focus when it's time entry.clutter_text.connect('key-focus-out', () => { 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 let text = clutter_text.text; clutter_text.text = ''; let {deviceId, requestReplyId, source} = this.notification; source._createApp((app, error) => { // Bail on error in case we can try again if (error !== null) return; let target = new GLib.Variant('(ssbv)', [ deviceId, 'replyNotification', true, new GLib.Variant('(ssa{ss})', [requestReplyId, text, {}]) ]); app.ActivateActionRemote( 'device', [target], NotificationDaemon.getPlatformData() ); 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. * * See: https://gitlab.gnome.org/GNOME/gnome-shell/blob/master/js/ui/notificationDaemon.js#L631-724 */ class Source extends NotificationDaemon.GtkNotificationDaemonAppSource { _closeGSConnectNotification(notification, reason) { if (reason !== MessageTray.NotificationDestroyedReason.DISMISSED) { return; } // TODO: Sometimes @notification is the object, sometimes it's the id? if (typeof notification === 'string') { notification = this.notifications[notification]; if (!notification) return; } // Avoid sending the request multiple times if (notification._remoteClosed) { return; } notification._remoteClosed = true; this._createApp((app, error) => { // Bail on error and reset in case we can try again if (error !== null) { notification._remoteClosed = false; return; } let target = new GLib.Variant('(ssbv)', [ notification.deviceId, 'closeNotification', true, new GLib.Variant('s', notification.remoteId) ]); app.ActivateActionRemote( 'device', [target], NotificationDaemon.getPlatformData() ); }); } /** * Override to control notification spawning * https://gitlab.gnome.org/GNOME/gnome-shell/blob/master/js/ui/notificationDaemon.js#L685-L703 */ addNotification(notificationId, notificationParams, showBanner) { let idMatch, deviceId, requestReplyId, remoteId, localId; // Check if it's a repliable device notification if ((idMatch = notificationId.match(REPLY_REGEX))) { [idMatch, deviceId, remoteId, requestReplyId] = idMatch; localId = `${deviceId}|${remoteId}`; // Check if it's a device notification } else if ((idMatch = notificationId.match(DEVICE_REGEX))) { [idMatch, deviceId, remoteId] = idMatch; localId = `${deviceId}|${remoteId}`; } else { localId = notificationId; } // this._notificationPending = true; let notification = this._notifications[localId]; // Check if @notificationParams represents an exact repeat let repeat = ( notification && notification.title === notificationParams.title.unpack() && notification.bannerBodyText === notificationParams.body.unpack() ); // If it's a repeat, we still update the metadata if (repeat) { notification.deviceId = deviceId; notification.remoteId = remoteId; notification.requestReplyId = requestReplyId; // Device Notification } else if (idMatch) { notification = new NotificationDaemon.GtkNotificationDaemonNotification(this, notificationParams); notification.deviceId = deviceId; notification.remoteId = remoteId; notification.requestReplyId = requestReplyId; notification.connect('destroy', (notification, reason) => { this._closeGSConnectNotification(notification, reason); delete this._notifications[localId]; }); this._notifications[localId] = notification; // Service Notification } else { notification = new NotificationDaemon.GtkNotificationDaemonNotification(this, notificationParams); notification.connect('destroy', (notification, reason) => { delete this._notifications[localId]; }); this._notifications[localId] = notification; } if (showBanner && !repeat) this.notify(notification); else this.pushNotification(notification); this._notificationPending = false; } /** * Override to lift the usual notification limit (3) * See: https://gitlab.gnome.org/GNOME/gnome-shell/blob/master/js/ui/messageTray.js#L773-L786 */ pushNotification(notification) { if (this.notifications.includes(notification)) return; notification.connect('destroy', this._onNotificationDestroy.bind(this)); notification.connect('acknowledged-changed', this.countUpdated.bind(this)); this.notifications.push(notification); this.emit('notification-added', notification); this.countUpdated(); } /** * Override to spawn repliable banners when appropriate * https://gitlab.gnome.org/GNOME/gnome-shell/blob/master/js/ui/messageTray.js#L745-L747 */ createBanner(notification) { if (notification.requestReplyId) { return new RepliableNotificationBanner(notification); } else { return new MessageTray.NotificationBanner(notification); } } } /** * If there is an active GtkNotificationDaemonAppSource for GSConnect when the * extension is loaded, it has to be patched in place. */ function patchGSConnectNotificationSource() { let source = Main.notificationDaemon._gtkNotificationDaemon._sources[gsconnect.app_id]; if (source !== undefined) { // Patch in the subclassed methods source._closeGSConnectNotification = Source.prototype._closeGSConnectNotification; source.addNotification = Source.prototype.addNotification; source.pushNotification = Source.prototype.pushNotification; source.createBanner = Source.prototype.createBanner; // Connect to existing notifications for (let [id, notification] of Object.entries(source._notifications)) { let _id = notification.connect('destroy', (notification, reason) => { source._closeGSConnectNotification(id, 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 = NotificationDaemon.GtkNotificationDaemon.prototype._ensureAppSource; const _ensureAppSource = function(appId) { let source = __ensureAppSource.call(this, appId); if (source._appId === 'org.gnome.Shell.Extensions.GSConnect') { source._closeGSConnectNotification = Source.prototype._closeGSConnectNotification; source.addNotification = Source.prototype.addNotification; source.pushNotification = Source.prototype.pushNotification; source.createBanner = Source.prototype.createBanner; } return source; }; function patchGtkNotificationDaemon() { NotificationDaemon.GtkNotificationDaemon.prototype._ensureAppSource = _ensureAppSource; } function unpatchGtkNotificationDaemon() { NotificationDaemon.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; function patchGtkNotificationSources() { // This should diverge as little as possible from the original let addNotification = function(notificationId, notificationParams, showBanner) { this._notificationPending = true; if (this._notifications[notificationId]) this._notifications[notificationId].destroy(); let notification = new NotificationDaemon.GtkNotificationDaemonNotification(this, notificationParams); notification.connect('destroy', (notification, reason) => { this._withdrawGSConnectNotification(notification, reason); delete this._notifications[notificationId]; }); this._notifications[notificationId] = notification; if (showBanner) this.notify(notification); else this.pushNotification(notification); this._notificationPending = false; }; let _createGSConnectApp = function(callback) { return new NotificationDaemon.FdoApplicationProxy( Gio.DBus.session, 'org.gnome.Shell.Extensions.GSConnect', '/org/gnome/Shell/Extensions/GSConnect', callback ); }; let _withdrawGSConnectNotification = function(id, notification, reason) { if (reason !== MessageTray.NotificationDestroyedReason.DISMISSED) { return; } // Avoid sending the request multiple times if (notification._remoteWithdrawn) { return; } notification._remoteWithdrawn = true; this._createGSConnectApp((app, error) => { // Bail on error and reset in case we can try again if (error !== null) { notification._remoteWithdrawn = false; return; } // Recreate the notification id as it would've been sent let target = new GLib.Variant('(ssbv)', [ '*', 'withdrawNotification', true, // Recreate the notification id as it would've been sent new GLib.Variant('s', `gtk|${this._appId}|${id}`) ]); app.ActivateActionRemote( 'device', [target], NotificationDaemon.getPlatformData() ); }); }; NotificationDaemon.GtkNotificationDaemonAppSource.prototype.addNotification = addNotification; NotificationDaemon.GtkNotificationDaemonAppSource.prototype._createGSConnectApp = _createGSConnectApp; NotificationDaemon.GtkNotificationDaemonAppSource.prototype._withdrawGSConnectNotification = _withdrawGSConnectNotification; } function unpatchGtkNotificationSources() { NotificationDaemon.GtkNotificationDaemonAppSource.prototype.addNotification = _addNotification; delete NotificationDaemon.GtkNotificationDaemonAppSource.prototype._createGSConnectApp; delete NotificationDaemon.GtkNotificationDaemonAppSource.prototype._withdrawGSConnectNotification; } gnome-shell-extension-gsconnect-20/src/shell/tooltip.js000066400000000000000000000203011341554142200234250ustar00rootroot00000000000000'use strict'; const Clutter = imports.gi.Clutter; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const Pango = imports.gi.Pango; const St = imports.gi.St; const Main = imports.ui.main; const Tweener = imports.ui.tweener; /** * 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 */ var TOOLTIP_BROWSE_ID = 0; var TOOLTIP_BROWSE_MODE = false; var Tooltip = class Tooltip { constructor(params) { // Properties Object.defineProperties(this, { 'custom': { get: () => this._custom || false, set: (actor) => { this._custom = actor; this._markup = null; this._text = null; this._update(); } }, 'markup': { get: () => this._markup || false, set: (value) => { this._markup = value; this._text = null; this._update(); } }, 'text': { get: () => this._text || false, set: (value) => { this._markup = null; this._text = value; this._update(); } }, 'icon_name': { get: () => this._gicon.name, set: (icon_name) => { if (!icon_name) { this.gicon = null; } else { this.gicon = new Gio.ThemedIcon({ name: icon_name }); } } }, 'gicon': { get: () => this._gicon || false, set: (gicon) => { this._gicon = gicon; this._update(); } }, 'x_offset': { get: () => (this._x_offset === undefined) ? 0 : this._x_offset, set: (offset) => { this._x_offset = (Number.isInteger(offset)) ? offset : 0; } }, 'y_offset': { get: () => (this._y_offset === undefined) ? 0 : this._y_offset, set: (offset) => { this._y_offset = (Number.isInteger(offset)) ? offset : 0; } } }); this._parent = params.parent; for (let param in params) { if (param !== 'parent') { this[param] = params[param]; } } this._hoverTimeoutId = 0; this._showing = false; // TODO: oddly fuzzy on menu items, sometimes if (this._parent.actor) { this._parent = this._parent.actor; } this._hoverId = this._parent.connect( 'notify::hover', this._onHover.bind(this) ); this._pressId = this._parent.connect( 'button-press-event', this._hide.bind(this) ); this._parent.connect('destroy', this.destroy.bind(this)); } _update() { if (this._showing) { this._show(); } } _show() { if (!this.text && !this.markup) { this._hide(); return; } if (!this.bin) { this.bin = new St.Bin({ style_class: 'osd-window gsconnect-tooltip', opacity: 232 }); if (this.custom) { this.bin.child = this.custom; } else { this.bin.child = new St.BoxLayout({vertical: false}); if (this.gicon) { this.bin.child.icon = new St.Icon({ gicon: this.gicon, y_align: St.Align.START }); this.bin.child.icon.set_y_align(Clutter.ActorAlign.START); this.bin.child.add_child(this.bin.child.icon); } this.label = new St.Label({text: this.markup || this.text}); this.label.clutter_text.line_wrap = true; this.label.clutter_text.line_wrap_mode = Pango.WrapMode.WORD; this.label.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; this.label.clutter_text.use_markup = (this.markup); this.bin.child.add_child(this.label); } Main.layoutManager.uiGroup.add_child(this.bin); Main.layoutManager.uiGroup.set_child_above_sibling(this.bin, null); } else if (this.custom) { this.bin.child = this.custom; } else { if (this.bin.child.icon) { this.bin.child.icon.destroy(); } if (this.gicon) { this.bin.child.icon = new St.Icon({gicon: this.gicon}); this.bin.child.insert_child_at_index(this.bin.child.icon, 0); } this.label.clutter_text.text = this.markup || this.text; this.label.clutter_text.use_markup = (this.markup); } // Position tooltip let [x, y] = this._parent.get_transformed_position(); x = (x + (this._parent.width / 2)) - Math.round(this.bin.width / 2); x += this.x_offset; y += this.y_offset; // Show tooltip if (this._showing) { Tweener.addTween(this.bin, { x: x, y: y, time: 0.15, transition: 'easeOutQuad' }); } else { this.bin.set_position(x, y); Tweener.addTween(this.bin, { opacity: 232, time: 0.15, transition: 'easeOutQuad' }); 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) { Tweener.addTween(this.bin, { opacity: 0, time: 0.10, transition: 'easeOutQuad', onComplete: () => { Main.layoutManager.uiGroup.remove_actor(this.bin); if (this.custom) { this.bin.remove_child(this.custom); } this.bin.destroy(); delete this.bin; } }); } 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._hoverId); this._parent.disconnect(this._pressId); if (this.custom) { this.custom.destroy(); } if (this.bin) { Main.layoutManager.uiGroup.remove_actor(this.bin); this.bin.destroy(); } if (this._hoverTimeoutId) { GLib.source_remove(this._hoverTimeoutId); this._hoverTimeoutId = 0; } } }; gnome-shell-extension-gsconnect-20/src/stylesheet.css000066400000000000000000000061671341554142200232070ustar00rootroot00000000000000 /* 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; } /* 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: none; border-radius: 16px; padding: 8px; } .gsconnect-icon-button:hover, .gsconnect-icon-button:focus { border: none; padding: 8px; } /* Do Not Disturb Dialog ModalDialog.ModalDialog.gsconnect-dnd-dialog StBoxLayout.nm-dialog-header-hbox StIcon.nm-dialog-header-icon StBoxLayout StLabel.nm-dialog-header StLabel.nm-dialog-subheader StBoxLayout.nm-dialog-content StBoxLayout.gsconnect-radio-list StBoxLayout.gsconnect-radio-button (Until you turn off...) StButton.pager-button StIcon StLabel StBoxLayout.gsconnect-radio-button (Until 00:00) StButton.pager-button StIcon StBoxLayout.gsconnect-dnd-timer StLabel StButton.pager-button StIcon StButton.pager-button StIcon */ .gsconnect-radio-list { spacing: 0.25em; } .gsconnect-radio-button { spacing: 0.25em; } .gsconnect-radio-button .pager-button { /* fix for arc-theme */ color: inherit; } .gsconnect-radio-button StIcon { icon-size: 16px; } .gsconnect-dnd-timer { spacing: 0.25em; } /* 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; } gnome-shell-extension-gsconnect-20/webextension/000077500000000000000000000000001341554142200222155ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/webextension/_locales/000077500000000000000000000000001341554142200237765ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/webextension/_locales/be/000077500000000000000000000000001341554142200243645ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/webextension/_locales/be/messages.json000066400000000000000000000032321341554142200270660ustar00rootroot00000000000000{ "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" } } gnome-shell-extension-gsconnect-20/webextension/_locales/cs/000077500000000000000000000000001341554142200244035ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/webextension/_locales/cs/messages.json000066400000000000000000000030411341554142200271030ustar00rootroot00000000000000{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "Sdílet odkazy se zařízeními a kontakty z webového prohlížeče", "description": "The extension description" }, "contextMenuMultipleDevices": { "message": "Poslat 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": "Zařízení je spárováno", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "Otevřít složku", "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" } } gnome-shell-extension-gsconnect-20/webextension/_locales/de/000077500000000000000000000000001341554142200243665ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/webextension/_locales/de/messages.json000066400000000000000000000030221341554142200270650ustar00rootroot00000000000000{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "Teile Links über GSConnect, direkt an den Browser oder per SMS.", "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": "Service 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" } } gnome-shell-extension-gsconnect-20/webextension/_locales/en/000077500000000000000000000000001341554142200244005ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/webextension/_locales/en/messages.json000066400000000000000000000027771341554142200271170ustar00rootroot00000000000000{ "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" } } gnome-shell-extension-gsconnect-20/webextension/_locales/es/000077500000000000000000000000001341554142200244055ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/webextension/_locales/es/messages.json000066400000000000000000000030441341554142200271100ustar00rootroot00000000000000{ "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": "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" } } gnome-shell-extension-gsconnect-20/webextension/_locales/et/000077500000000000000000000000001341554142200244065ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/webextension/_locales/et/messages.json000066400000000000000000000027751341554142200271230ustar00rootroot00000000000000{ "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" } } gnome-shell-extension-gsconnect-20/webextension/_locales/fr/000077500000000000000000000000001341554142200244055ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/webextension/_locales/fr/messages.json000066400000000000000000000030401341554142200271040ustar00rootroot00000000000000{ "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": "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" } } gnome-shell-extension-gsconnect-20/webextension/_locales/hu/000077500000000000000000000000001341554142200244125ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/webextension/_locales/hu/messages.json000066400000000000000000000030461341554142200271170ustar00rootroot00000000000000{ "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": "Küldés mobil eszkö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" } } gnome-shell-extension-gsconnect-20/webextension/_locales/it/000077500000000000000000000000001341554142200244125ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/webextension/_locales/it/messages.json000066400000000000000000000030411341554142200271120ustar00rootroot00000000000000{ "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" } } gnome-shell-extension-gsconnect-20/webextension/_locales/lt/000077500000000000000000000000001341554142200244155ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/webextension/_locales/lt/messages.json000066400000000000000000000030461341554142200271220ustar00rootroot00000000000000{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "Bendrinti nuorodas iš saityno naršyklės su įrenginiais ir adresatais", "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": "Įrenginys yra išporuotas", "description": "Popup Menu label when no devices are connected or have supported plugins enabled" }, "shareMessage": { "message": "Atverti aplanką", "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" } } gnome-shell-extension-gsconnect-20/webextension/_locales/nl-BE/000077500000000000000000000000001341554142200246735ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/webextension/_locales/nl-BE/messages.json000066400000000000000000000030041341554142200273720ustar00rootroot00000000000000{ "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" } } gnome-shell-extension-gsconnect-20/webextension/_locales/nl-NL/000077500000000000000000000000001341554142200247165ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/webextension/_locales/nl-NL/messages.json000066400000000000000000000030321341554142200274160ustar00rootroot00000000000000{ "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" } } gnome-shell-extension-gsconnect-20/webextension/_locales/pl/000077500000000000000000000000001341554142200244115ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/webextension/_locales/pl/messages.json000066400000000000000000000031361341554142200271160ustar00rootroot00000000000000{ "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" } } gnome-shell-extension-gsconnect-20/webextension/_locales/pt-BR/000077500000000000000000000000001341554142200247225ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/webextension/_locales/pt-BR/messages.json000066400000000000000000000030511341554142200274230ustar00rootroot00000000000000{ "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" } } gnome-shell-extension-gsconnect-20/webextension/_locales/ru/000077500000000000000000000000001341554142200244245ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/webextension/_locales/ru/messages.json000066400000000000000000000031601341554142200271260ustar00rootroot00000000000000{ "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" } } gnome-shell-extension-gsconnect-20/webextension/_locales/sk/000077500000000000000000000000001341554142200244135ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/webextension/_locales/sk/messages.json000066400000000000000000000030761341554142200271230ustar00rootroot00000000000000{ "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" } } gnome-shell-extension-gsconnect-20/webextension/_locales/sr-Latn/000077500000000000000000000000001341554142200253165ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/webextension/_locales/sr-Latn/messages.json000066400000000000000000000030251341554142200300200ustar00rootroot00000000000000{ "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" } } gnome-shell-extension-gsconnect-20/webextension/_locales/sr/000077500000000000000000000000001341554142200244225ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/webextension/_locales/sr/messages.json000066400000000000000000000032161341554142200271260ustar00rootroot00000000000000{ "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" } } gnome-shell-extension-gsconnect-20/webextension/_locales/tr/000077500000000000000000000000001341554142200244235ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/webextension/_locales/tr/messages.json000066400000000000000000000030301341554142200271210ustar00rootroot00000000000000{ "extensionName": { "message": "GSConnect", "description": "The extension name" }, "extensionDescription": { "message": "GSConnect ile bağlantıları doğrudan tarayıcı veya SMS ile paylaşın.", "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 Kullanılamıyor", "description": "Popup Menu label when disconnected from the GNOME Shell extension" }, "popupMenuNoDevices": { "message": "Aygıt 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" } } gnome-shell-extension-gsconnect-20/webextension/background.html000066400000000000000000000003641341554142200252250ustar00rootroot00000000000000 gnome-shell-extension-gsconnect-20/webextension/gettext.js000077500000000000000000000070361341554142200242500ustar00rootroot00000000000000#!/usr/bin/env gjs 'use strict'; const ByteArray = imports.byteArray; const Gio = imports.gi.Gio; const GLib = imports.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 { let data = gfile.load_contents(null)[1]; if (data instanceof Uint8Array) { return JSON.parse(ByteArray.toString(data)); } else { return JSON.parse(data.toString()); } } catch (e) { logError(e); return {}; } }; // Find the cwd, locale dir and po dir let cwd = Gio.File.new_for_path('.'); let localedir = cwd.get_child('_locales'); let 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, iter = podir.enumerate_children('standard::name', 0, null); while ((info = iter.next_file(null))) { let [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 let jsondir = localedir.get_child(langCode); let 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 = (po instanceof Uint8Array) ? ByteArray.toString(po) : po.toString(); for (let 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); } gnome-shell-extension-gsconnect-20/webextension/images/000077500000000000000000000000001341554142200234625ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/webextension/images/desktop.svg000066400000000000000000000004221341554142200256520ustar00rootroot00000000000000 gnome-shell-extension-gsconnect-20/webextension/images/gsconnect-128.png000066400000000000000000000025001341554142200264600ustar00rootroot00000000000000PNG  IHDR>asBIT|d pHYs;;̶tEXtSoftwarewww.inkscape.org<IDATx=oEgf;Cp!++J,h4H4A`޿BJ$y))\C˅-ErAPĖE zvgvU9{_s{F5hWt/ `Hd6J=LJ[|b-!J}6QΟ\@ndwا0ݰݠI˶4 EH~-5|tТ,srhR |1В,hВ, 0Ȳ Jy=8gҞCR$T?p/ObSGT$U?SoO䁘TqNNY]f*7*`۠>2#U1޻y ;:_) [][Z!pAwN3O̴d 004|Iկzؿ]A? ]Z>=Z˴³S @!^{n74U?ҰY B4Ȑfd,xo`,vok XIaMb~,('1 I b~تcڳX$`Xkb~0EObI W?) DOb*R~PBT?R~p.'T$U?@zOb^@OJ>)V?LJOJ'1IMp| IM| I(C'Y?x<>Dè0Sr~#姛J᣶XOZo'-.~;|U?inUW.O8}>T }T;>I'_qAr''`Jl$!T?A =̒41Fi?ltmw5Xb0ƌT1EfꙈRo/Qy*(gU=xV׮X=wwV OFGFIcK612L>Pzzc8"_~'c(\QpV1 ~Vz}?}BDDDDDDDDDDDDDDDDDDDDA>IENDB`gnome-shell-extension-gsconnect-20/webextension/images/gsconnect-16.png000066400000000000000000000006411341554142200264000ustar00rootroot00000000000000PNG  IHDRasBIT|d pHYsbb8ztEXtSoftwarewww.inkscape.org<IDAT8Ő1KBa*A"!mlh PZ74Қw) )47$9 vE!z93A~S ˷w 3 g_reLUT>NDD[#n_n:~/!˙D+j:@U[pǃ kpf0Ԫ>?Sm?a&J̧IENDB`gnome-shell-extension-gsconnect-20/webextension/images/gsconnect-22.png000066400000000000000000000011011341554142200263650ustar00rootroot00000000000000PNG  IHDRĴl;sBIT|d pHYs & &Q3tEXtSoftwarewww.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@Ag0ll2i"*o{yqI}'*ǹhYsiw]:G7cLŅ7l>:Bm}zs IENDB`gnome-shell-extension-gsconnect-20/webextension/images/gsconnect-24.png000066400000000000000000000011251341554142200263750ustar00rootroot00000000000000PNG  IHDRw=sBIT|d pHYs  tEXtSoftwarewww.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 a(&GsL\6cARQ=nZJv &ZK!b%M? nvA-N AlP۟8^XvV \IENDB`gnome-shell-extension-gsconnect-20/webextension/images/gsconnect-256.png000066400000000000000000000053041341554142200264670ustar00rootroot00000000000000PNG  IHDR\rfsBIT|d pHYsvvxtEXtSoftwarewww.inkscape.org< AIDATx_hwybҤmou"sv*EE/-n"" :00Y\fk?S*d!,solsIٞ_4*,."e }>B_@<_c$Y^1 Iy>T_h< |y. 3* )YmUx&_$W= U@2Hcҟ @@H"z?Db5ҟ_ E@H1/ aDa_8 C @THa1 /, dzhc_=DWs! ewdkrsmM?|zML*U)+ ,w}y>ڕI;;;2+tMz^%ᰈN8~g˧>\pRP;+s\/B=#Fe˯ʭ:QGǎ}2eP++G:MԦL[ZE9A jS&\0N[`P2omm-IAd@Ԣ>1:OOWu e?sOGkSÇ?G S=!RM{l)?W RN]Hӟ`ҟ-1؅gK=i gkBӚH&?hJx!"24444H?_J#ٚ迲_|#Ś4}41i ?14&?_!MN?-lP?-|@%ڐ@%ڐ4fD:@ڐ4tEdujKӂ|[DjkՖ͍'/H75A)i,,,l_әN:q!"mJx .Ϳ."<@`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"D3eVaaS(uIENDB`gnome-shell-extension-gsconnect-20/webextension/images/gsconnect-32.png000066400000000000000000000007731341554142200264040ustar00rootroot00000000000000PNG  IHDR szzsBIT|d pHYs+tEXtSoftwarewww.inkscape.org<xIDATX1N@DZHDB QpD"q :(q\J !hmMycx2Cp?`>^pf.Mqjr`fhd_7BN9XˊDa"*5ړq+F`Tb^5 whhX>_rr)AMg #@~2b'Nt3:Snwx{ ٝW͘ Kαc10bӋ!N/#:n nVY[]- (?~N/m+iQQ ^/*Uw$:\B7ȠQZo`HAӋ!G'@.7t [h{@ )&:ַiwӧn Zdp GC='iX,>j ꃭ{{/q7ZW =_)$jcDir]UntLRRRRp(zIENDB`gnome-shell-extension-gsconnect-20/webextension/images/gsconnect-64.png000066400000000000000000000014171341554142200264050ustar00rootroot00000000000000PNG  IHDR@@iqsBIT|d pHYsetEXtSoftwarewww.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.!Ee7 3<>8A }>N0|Fy> B"oqZFqZF- a㴾lnqfͺ AIqV{q:M48Kb<`}R@lo}L&7WH+ƍyIq*Dz?~~ʎxZ\[uac{'ޙ^~wh/@`tph:J$D"HD/Y4IENDB`gnome-shell-extension-gsconnect-20/webextension/images/gsconnect.svg000066400000000000000000000044621341554142200261740ustar00rootroot00000000000000 image/svg+xml gnome-shell-extension-gsconnect-20/webextension/images/laptop.svg000066400000000000000000000006711341554142200255060ustar00rootroot00000000000000 gnome-shell-extension-gsconnect-20/webextension/images/message.svg000066400000000000000000000004321341554142200256260ustar00rootroot00000000000000 gnome-shell-extension-gsconnect-20/webextension/images/open-in-browser.svg000066400000000000000000000004411341554142200272300ustar00rootroot00000000000000 gnome-shell-extension-gsconnect-20/webextension/images/phone.svg000066400000000000000000000004431341554142200253150ustar00rootroot00000000000000 gnome-shell-extension-gsconnect-20/webextension/images/tablet.svg000066400000000000000000000007211341554142200254560ustar00rootroot00000000000000 gnome-shell-extension-gsconnect-20/webextension/js/000077500000000000000000000000001341554142200226315ustar00rootroot00000000000000gnome-shell-extension-gsconnect-20/webextension/js/background.js000066400000000000000000000252461341554142200253170ustar00rootroot00000000000000/*eslint no-console: ["error", { allow: ["warn", "error"] }] */ '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.' ]; var CONNECTED = false; var DEVICES = []; var PORT = null; var reconnectDelay = 100; var reconnectTimer = null; var reconnectResetTimer = null; // Simple error logging function function logError(error) { if (_MUTE.includes(error.message)) return; console.error(error.message); } function toggleAction(tab = null) { try { // Disable on "about:" pages if (_ABOUT.test(tab.url)) { browser.browserAction.disable(tab.id); } else { browser.browserAction.enable(tab.id); } } catch (e) { browser.browserAction.disable(); } } /** * Send a message to the native-messaging-host */ async function postMessage(message) { try { // console.log(`WebExtension SEND: ${JSON.stringify(message)}`); if (!PORT || !message || !message.type) { console.warn('Missing message parameters'); return; } await PORT.postMessage(message); } catch (e) { logError(e); } } /** * Forward a message from the browserAction popup to the NMH */ async function onPopupMessage(message, sender, sendResponse) { try { if (sender.url.includes('/popup.html')) { await postMessage(message); } } catch (e) { logError(e); } } /** * Forward a message from the NMH to the browserAction popup */ async function forwardPortMessage(message) { try { await browser.runtime.sendMessage(message); } catch (e) { logError(e); } } /** * Context Menu Item Callback * * @param {menus.OnClickData} info - Information about the item and context * @param {tabs.Tab} tab - The details of the tab where the click took place */ async function onContextItem(info, tab) { try { let [id, action] = info.menuItemId.split(':'); await postMessage({ type: 'share', data: { device: id, url: info.linkUrl || info.srcUrl || info.frameUrl || info.pageUrl, action: action } }); } catch (e) { logError(e); } } /** * Populate the context menu * * @param {tabs.Tab} tab - The current tab */ async function createContextMenu(tab) { try { // Clear context menu await browser.contextMenus.removeAll(); // Bail on "about:" page or no devices if (_ABOUT.test(tab.url) || DEVICES.length === 0) return; // Multiple devices; we'll have at least one submenu level if (DEVICES.length > 1) { await browser.contextMenus.create({ id: 'contextMenuMultipleDevices', title: browser.i18n.getMessage('contextMenuMultipleDevices'), contexts: _CONTEXTS }); for (let device of 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 { let device = 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 */ 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') { CONNECTED = message.data; if (CONNECTED) { postMessage({type: 'devices'}); } else { DEVICES = []; } // We're being sent a list of devices (so the NMH must be connected) } else if (message.type === 'devices') { CONNECTED = true; DEVICES = message.data; } // Forward the message to popup.html forwardPortMessage(message); // let tabs = await browser.tabs.query({ active: true, currentWindow: true }); createContextMenu(tabs[0]); } catch (e) { logError(e); } } /** * Callback for disconnection from the native-messaging-host * * @param {object} port - The port that is now invalid */ async function onDisconnect(port) { try { CONNECTED = false; 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) { let message = browser.runtime.lastError.message; console.warn(`Disconnected: ${message}`); } // Exponential back-off on reconnect reconnectTimer = window.setTimeout(connect, reconnectDelay); reconnectDelay = reconnectDelay * 2; } catch (e) { logError(e); } } /** * Start and/or connect to the native-messaging-host */ async function connect() { try { 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 PORT.onDisconnect.addListener(onDisconnect); PORT.onMessage.addListener(onPortMessage); await 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(); gnome-shell-extension-gsconnect-20/webextension/js/browser-polyfill.min.js000066400000000000000000000234171341554142200272730ustar00rootroot00000000000000(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}})(this,function(a){"use strict";if("undefined"==typeof browser||Object.getPrototypeOf(browser)!==Object.prototype){a.exports=(()=>{const e={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}},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},getBrowserInfo:{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(e).length)throw new Error("api-metadata.json has not been included in browser-polyfill");class f extends WeakMap{constructor(u,v=void 0){super(v),this.createItem=u}get(u){return this.has(u)||this.set(u,this.createItem(u)),super.get(u)}}const g=u=>{return u&&"object"==typeof u&&"function"==typeof u.then},h=(u,v)=>{return(...w)=>{chrome.runtime.lastError?u.reject(chrome.runtime.lastError):v.singleCallbackArg||1>=w.length?u.resolve(w[0]):u.resolve(w)}},i=u=>1==u?"argument":"arguments",j=(u,v)=>{return function(x,...y){if(y.lengthv.maxArgs)throw new Error(`Expected at most ${v.maxArgs} ${i(v.maxArgs)} for ${u}(), got ${y.length}`);return new Promise((z,A)=>{if(v.fallbackToNoCallback)try{x[u](...y,h({resolve:z,reject:A},v))}catch(B){console.warn(`${u} API method doesn't seem to support the callback parameter, `+"falling back to call it without a callback: ",B),x[u](...y),v.fallbackToNoCallback=!1,v.noCallback=!0,z()}else v.noCallback?(x[u](...y),z()):x[u](...y,h({resolve:z,reject:A},v))})}},k=(u,v,w)=>{return new Proxy(v,{apply(x,y,z){return w.call(y,u,...z)}})};let l=Function.call.bind(Object.prototype.hasOwnProperty);const m=(u,v={},w={})=>{let x=Object.create(null),y={has(A,B){return B in u||B in x},get(A,B){if(B in x)return x[B];if(B in u){let D=u[B];if("function"==typeof D){if("function"==typeof v[B])D=k(u,u[B],v[B]);else if(l(w,B)){let E=j(B,w[B]);D=k(u,u[B],E)}else D=D.bind(u);}else if("object"==typeof D&&null!==D&&(l(v,B)||l(w,B)))D=m(D,v[B],w[B]);else return Object.defineProperty(x,B,{configurable:!0,enumerable:!0,get(){return u[B]},set(E){u[B]=E}}),D;return x[B]=D,D}},set(A,B,C){return B in x?x[B]=C:u[B]=C,!0},defineProperty(A,B,C){return Reflect.defineProperty(x,B,C)},deleteProperty(A,B){return Reflect.deleteProperty(x,B)}},z=Object.create(u);return new Proxy(z,y)},n=u=>({addListener(v,w,...x){v.addListener(u.get(w),...x)},hasListener(v,w){return v.hasListener(u.get(w))},removeListener(v,w){v.removeListener(u.get(w))}});let o=!1;const p=new f(u=>{return"function"==typeof u?function(w,x,y){let A,C,z=!1,B=new Promise(F=>{A=function(G){o||(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),o=!0),z=!0,F(G)}});try{C=u(w,x,A)}catch(F){C=Promise.reject(F)}const D=!0!==C&&g(C);if(!0!==C&&!D&&!z)return!1;const E=F=>{F.then(G=>{y(G)},G=>{let H;H=G&&(G instanceof Error||"string"==typeof G.message)?G.message:"An unexpected error occurred",y({__mozWebExtensionPolyfillReject__:!0,message:H})}).catch(G=>{console.error("Failed to send onMessage rejected reply",G)})};return D?E(C):E(B),!0}:u}),q=({reject:u,resolve:v},w)=>{chrome.runtime.lastError?chrome.runtime.lastError.message==="The message port closed before a response was received."?v():u(chrome.runtime.lastError):w&&w.__mozWebExtensionPolyfillReject__?u(new Error(w.message)):v(w)},r=(u,v,w,...x)=>{if(x.lengthv.maxArgs)throw new Error(`Expected at most ${v.maxArgs} ${i(v.maxArgs)} for ${u}(), got ${x.length}`);return new Promise((y,z)=>{const A=q.bind(null,{resolve:y,reject:z});x.push(A),w.sendMessage(...x)})},s={runtime:{onMessage:n(p),onMessageExternal:n(p),sendMessage:r.bind(null,"sendMessage",{minArgs:1,maxArgs:3})},tabs:{sendMessage:r.bind(null,"sendMessage",{minArgs:2,maxArgs:3})}},t={clear:{minArgs:1,maxArgs:1},get:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}};return e.privacy={network:{networkPredictionEnabled:t,webRTCIPHandlingPolicy:t},services:{passwordSavingEnabled:t},websites:{hyperlinkAuditingEnabled:t,referrersEnabled:t}},m(chrome,s,e)})()}else a.exports=browser}); //# sourceMappingURL=browser-polyfill.min.js.map // webextension-polyfill v.0.3.1 (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/. */gnome-shell-extension-gsconnect-20/webextension/js/popup.js000066400000000000000000000115261341554142200243370ustar00rootroot00000000000000/*eslint no-console: ["error", { allow: ["warn", "error"] }] */ 'use strict'; var CONNECTED = false; var DEVICES = []; var 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 function logError(error) { if (_MUTE.includes(error.message)) return; console.error(error.message); } /** * Share a URL, either direct to the browser or by SMS * * @param {string} device - The deviceId * @param {string} action - Currently either 'share' or 'telephony' * @param {string} url - The URL to share */ async function sendUrl(device, action, url) { try { window.close(); await browser.runtime.sendMessage({ type: 'share', data: { device: device, url: url, action: action } }); } catch (e) { logError(e); } } /** * Create and return a device element for the popup menu * * @param {object} device - A JSON object describing a connected device * @return {HTMLElement} - A
element with icon, name and actions */ function getDeviceElement(device) { let deviceElement = document.createElement('div'); deviceElement.className = 'device'; let deviceIcon = document.createElement('img'); deviceIcon.className = 'device-icon'; deviceIcon.src = `images/${device.type}.svg`; deviceElement.appendChild(deviceIcon); let deviceName = document.createElement('span'); deviceName.className = 'device-name'; deviceName.textContent = device.name; deviceElement.appendChild(deviceName); if (device.share) { let 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) { let 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() { let devNode = document.getElementById('popup'); while (devNode.hasChildNodes()) { devNode.removeChild(devNode.lastChild); } if (CONNECTED && DEVICES.length) { for (let device of DEVICES) { let deviceElement = getDeviceElement(device); devNode.appendChild(deviceElement); } return; } // Disconnected or no devices let message = document.createElement('span'); message.className = 'popup-menu-message'; devNode.appendChild(message); // The native-messaging-host or service is disconnected if (!CONNECTED) { message.textContent = browser.i18n.getMessage('popupMenuDisconnected'); // There are no devices } else { message.textContent = browser.i18n.getMessage('popupMenuNoDevices'); } } /** * Callback for receiving a message forwarded by background.js * * @param {object] message - A JSON message object * @param {runtime.MessageSender} sender - Tthe sender of the message. */ async 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 { let tabs = await browser.tabs.query({ active: true, currentWindow: true }); if (tabs.length) { 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); gnome-shell-extension-gsconnect-20/webextension/manifest.chrome.json000066400000000000000000000021671341554142200262000ustar00rootroot00000000000000{ "manifest_version": 2, "name": "__MSG_extensionName__", "description": "__MSG_extensionDescription__", "version": "5", "homepage_url": "https://github.com/andyholmes/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" } } gnome-shell-extension-gsconnect-20/webextension/manifest.firefox.json000066400000000000000000000016141341554142200263610ustar00rootroot00000000000000{ "manifest_version": 2, "name": "__MSG_extensionName__", "description": "__MSG_extensionDescription__", "version": "5", "homepage_url": "https://github.com/andyholmes/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" } } gnome-shell-extension-gsconnect-20/webextension/mkwebext.sh000077500000000000000000000042351341554142200244060ustar00rootroot00000000000000#!/bin/bash # 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 # Run eslinst on source elif [ "${1}" == "lint" ]; then echo "Running eslint..." eslint --global 'browser,document,console' js/background.js js/popup.js 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 -R js ${1}/ cp -R _locales ${1}/ cp background.html ${1}/ cp manifest.${1}.json ${1}/manifest.json cp popup.html ${1}/ cp stylesheet.css ${1}/ 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|lint]" 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 " lint Run eslint on the WebExtension source" echo "" echo "Building the Mozilla Firefox extension requires the 'web-ext' node module." exit 1 gnome-shell-extension-gsconnect-20/webextension/popup.html000066400000000000000000000004711341554142200242500ustar00rootroot00000000000000