diff --git a/.cmake-format.yaml b/.cmake-format.yaml new file mode 100644 index 00000000..b103a70c --- /dev/null +++ b/.cmake-format.yaml @@ -0,0 +1,9 @@ +bullet_char: '*' +dangle_parens: false +enum_char: . +line_ending: unix +line_width: 120 +max_pargs_hwrap: 3 +separate_ctrl_name_with_space: true +separate_fn_name_with_space: false +tab_size: 4 \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..206aebcb --- /dev/null +++ b/.editorconfig @@ -0,0 +1,3 @@ +[*.{sh,yml,yaml}] +indent_style = space +indent_size = 2 diff --git a/.gitignore b/.gitignore index 285d54f6..00f787d2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,33 +1,40 @@ +# Build files Makefile Makefile.* -*.user -*.user.* +CMakeLists.txt.user +build/ debug release -*.pdb -*.app -.packagingrc -.qmake.stash -.qmake.cache -.DS_Store + +# Build artifacts *.exe *.dll +*.pdb +*.so +*.a *.o +*.app +*.dylib *.moc -tests/*/tst_* -!tst_*.pro -!tst_*.cpp -!tst_*.h + +# Generated source files (These should always end up in the build dir anyway) *.pb.h *.pb.cc -packaging/installer/Qt -packaging/installer/Output -translation/*.qm +moc_*.cpp +moc_predefs.h +qrc_*.cpp + +# Generated translations +*.qm + +# Other +*.user +*.user.* +.DS_Store +.packagingrc +.vscode/ + +# Ricochet files (when running in the root dir) config config.ricochet -config.log -config.tests/*/* -!config.tests/*/*.pro -!config.tests/*/*.cpp -!config.tests/*/*.h -ricochet + diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..9e250b5e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "src/extern/fmt"] + path = src/extern/fmt + url = https://github.com/fmtlib/fmt.git +[submodule "src/extern/tor"] + path = src/extern/tor + url = https://git.torproject.org/tor.git diff --git a/.travis.yml b/.travis.yml index d1d5dd1f..6d216ce7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,31 +1,102 @@ language: cpp -dist: trusty +dist: bionic sudo: required - -os: - - linux - - osx - +git: + submodules: false env: - - CONFIG=Debug - # - CONFIG=Release + global: + - HEADLESS=True + - SKIP_CODESIGN=1 + - CONFIG=Release +matrix: + include: + - os: osx + env: + - BUILD_TARGET=osx + - USE_LOCAL_QT=YES + - USE_LOCAL_PROTOBUF=YES + - os: linux + env: + - BUILD_TARGET=linux-static + # - USE_LOCAL_QT=YES +addons: + homebrew: + packages: + - qt5 + - protobuf + - pkg-config + update: true + apt: + packages: + - automake + - autotools-dev + - build-essential + - ca-certificates + - curl + - git + - libasound2-dev + - libevent-dev + - libfontconfig1 + - libfontconfig1-dev + - libfreetype6 + - libfreetype6-dev + - libglu1-mesa-dev + - libprotobuf-dev + - libprotobuf-dev + - libqt5gui5 + - libqt5opengl5 + - libqt5opengl5-dev + - libqt5svg5 + - libssl-dev + - libtool + - libx11-xcb-dev + - '^libxcb.*' + - '^libxkb.*' + - libxrender-dev + - libxrender1 + - pkg-config + - protobuf-compiler + - python + - qt5-default + - qt5-qmake + - qtbase5-dev + - qtdeclarative5-dev + - qttools5-dev-tools + - tor + - unzip + - vim + - xkb-data + - xvfb + - zip install: - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then - sudo apt-get update - && sudo apt-get install -qq libssl-dev libprotobuf-dev protobuf-compiler - && sudo apt-get install -qq qt5-qmake qt5-default qtbase5-dev qttools5-dev-tools qtdeclarative5-dev - ; - fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then - brew update - && brew install qt5 - && brew install protobuf - && brew install openssl - ; - fi - +- (cd buildscripts && "${BUILD_TARGET}/build-deps.sh") script: - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then qmake -qt=qt5 CONFIG+=$CONFIG; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then /usr/local/opt/qt5/bin/qmake CONFIG+=$CONFIG OPENSSLDIR=/usr/local/opt/openssl PROTOBUFDIR=/usr/local/opt/protobuf; fi - - make +- (cd buildscripts && "${BUILD_TARGET}/test.sh") +- (cd buildscripts && "${BUILD_TARGET}/build.sh") +- export BUILD_NAME=$(cd buildscripts && "./get_build_name.sh") +deploy: + - provider: releases + name: DRAFT ${BUILD_NAME} + api_key: + secure: lIHMpIYSRcQAzFnbKeucjndUCzCVH78CZ7JpgB7BpUSO/F61ngdW9oWy8oJWdMb1JU0efSRRYNXbX4lTVBOS/iueqfoL6jNojT7t8yVgM31ZfnzJipqdtUHBDHHUHDEQez7MXudUpa9Zo6yCoHF8IT30NiKErbi7jG5YIEaxYzMR2ICWSkechFbGwe6nFgVW8iceNBW8GyyeBpkII7arTpJbZZlrcWDh+Ej5r6fHivV7oifuhSZ6VUYT3dB0tOqcHPU8OtnFpq1sQOtNwr+KNBEfBGLd+gyCXTdjmUebBXnSI6nR3hKEEQR87znlqFrzYKq2OwXqtYkTtc7MpSbmzH9bfxqC4WUa/8QL77cgLJWzhAaOe6+nqeadEJFEFB31KLOlt2SbsMyBy2xWi+NS43VOVz6FecbhgODq7QpXBN5kvN6kjc5kc7xY8joQW5k939/kN5P4DfpePd//bvmUzCVdYaak2pPKLzsyY5dXGqk7z3lGVE3h5Q+8EOnbsEvSrbhNF+Svm/FAEbjUMnaAx20o66MxPxvbb+I0iNT72+jqJypZLv5mjkw2ItMJR1G5zD95qxgZakzJ3liSSpGROBzpLSGQb8GQpOktY9UzocIM0pqyawg2Hfn6y1D9Ptp1SfOZ8rFJvqI+Mxv3FFp+lD3tOV6MvFj1+EJ8PiHyIQc= + skip_cleanup: true + file_glob: true + file: + - 'buildscripts/output/Ricochet-Refresh-*.dmg' + - 'buildscripts/output/ricochet-refresh-*-static.tar.bz2' + overwrite: true + draft: true + - provider: releases + name: PRERELEASE ${BUILD_NAME} + api_key: + secure: lIHMpIYSRcQAzFnbKeucjndUCzCVH78CZ7JpgB7BpUSO/F61ngdW9oWy8oJWdMb1JU0efSRRYNXbX4lTVBOS/iueqfoL6jNojT7t8yVgM31ZfnzJipqdtUHBDHHUHDEQez7MXudUpa9Zo6yCoHF8IT30NiKErbi7jG5YIEaxYzMR2ICWSkechFbGwe6nFgVW8iceNBW8GyyeBpkII7arTpJbZZlrcWDh+Ej5r6fHivV7oifuhSZ6VUYT3dB0tOqcHPU8OtnFpq1sQOtNwr+KNBEfBGLd+gyCXTdjmUebBXnSI6nR3hKEEQR87znlqFrzYKq2OwXqtYkTtc7MpSbmzH9bfxqC4WUa/8QL77cgLJWzhAaOe6+nqeadEJFEFB31KLOlt2SbsMyBy2xWi+NS43VOVz6FecbhgODq7QpXBN5kvN6kjc5kc7xY8joQW5k939/kN5P4DfpePd//bvmUzCVdYaak2pPKLzsyY5dXGqk7z3lGVE3h5Q+8EOnbsEvSrbhNF+Svm/FAEbjUMnaAx20o66MxPxvbb+I0iNT72+jqJypZLv5mjkw2ItMJR1G5zD95qxgZakzJ3liSSpGROBzpLSGQb8GQpOktY9UzocIM0pqyawg2Hfn6y1D9Ptp1SfOZ8rFJvqI+Mxv3FFp+lD3tOV6MvFj1+EJ8PiHyIQc= + skip_cleanup: true + file_glob: true + file: + - 'buildscripts/output/Ricochet-Refresh-*.dmg' + - 'buildscripts/output/ricochet-refresh-*-static.tar.bz2' + on: + tags: true + overwrite: true + prerelease: true diff --git a/AUTHORS.md b/AUTHORS.md index b27deb4b..718c5d42 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -1,6 +1,9 @@ ### Development * John Brooks (@special) `` - +* Derwent Mcelhinney (@derwentx) +* Peter Tonoli (@petertonoli) +* Richard Pospesel (richard on #tor-dev @OFTC) `` +* Marco Simonelli (msim on #tor-dev @OFTC) `` ### Sponsors * Blueprint for Free Speech - https://blueprintforfreespeech.net/ diff --git a/BUILDING.md b/BUILDING.md index 873aa40e..bb5f3bf6 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -1,16 +1,45 @@ -## Building Ricochet +## New Building -These instructions are intended for people who wish to build or modify Ricochet from source. Most users should [download releases](https://github.com/ricochet-im/ricochet/releases) instead. +Cloning must be done with `--recurse-submodules`: -Clone with git from `https://github.com/ricochet-im/ricochet.git`, or download source packages [on github](https://github.com/ricochet-im/ricochet/releases). Then proceed to instructions for your platform. +```sh +git clone --recurse-submodules https://github.com/blueprint-freespeech/ricochet-refresh.git +``` +In the event that you cloned the repo without fetching the submodules, you can still get them with: +```sh +git submodule --init --update +``` + +Later, you should update your local repository with: +```sh +git pull --recurse-submodules +``` -If you're interested in helping to package Ricochet for common Linux platforms, please get in touch! +### Linux -## Hints +Clone -Add `CONFIG+=debug` or `CONFIG+=release` to the qmake command for a debug or release build. Debug builds enable logging to standard output, and shouldn't be used in sensitive environments. +Download and install the prebuilt Qt 5.15 ( https://www.qt.io/download-qt-installer ) and add bin directory containing 'qmake' to your PATH environment variable. + +#### Debian + +Install the following development packages: -By default, Ricochet will be portable, and configuration is stored in a folder named `config` next to the binary. Add `DEFINES+=RICOCHET_NO_PORTABLE` to the qmake command for a system-wide installation using platform configuration paths instead. +```sh +apt-get install build-essential libssl-dev pkg-config libprotobuf-dev protobuf-compiler libgl-dev +``` + +## Building Ricochet Refresh + +These instructions are intended for people who wish to build or modify Ricochet Refresh from source. Most users should [download releases](https://github.com/blueprint-freespeech/ricochet-refresh/releases) instead. + +Clone with git from `https://github.com/blueprint-freespeech/ricochet-refresh.git`, or download source packages [on github](https://github.com/blueprint-freespeech/ricochet-refresh/releases). Then proceed to instructions for your platform. + +If you're interested in helping to package Ricochet Refresh for common Linux platforms, please get in touch! + +## Debug vs Release build + +Add `CONFIG+=debug` or `CONFIG+=release` to the qmake command for a debug or release build. Debug builds enable logging to standard output, and shouldn't be used in sensitive environments. ## Linux @@ -39,6 +68,7 @@ If the `qml-module-qtquick` packages aren't available, try `qtdeclarative5-contr #### Qt SDK The [Qt SDK](https://www.qt.io/download/) is available for most Linux systems and includes an IDE as well as all Qt dependencies. +### Building and Installing To build, simply run: ```sh qmake # qmake-qt5 for some platforms @@ -47,19 +77,17 @@ make For a system-wide installation, use: ```sh -qmake DEFINES+=RICOCHET_NO_PORTABLE -make make install # as root ``` -You must have a `tor` binary installed on the system (in $PATH), or placed next to the `ricochet` binary. +You must have a `tor` binary installed on the system (in `$PATH`), or placed next to the `ricochet-refresh` binary. -In portable mode (default), all configuration is stored in a folder called `config` with the binary. When installed, the platform's user configuration path is used instead. +By default Ricochet Refresh will load and save configuration files to `~/.local/share/ricochet-refresh/`. First argument to command-line overrides this and allows you to specificy the config directory. -The [buildscripts](https://github.com/ricochet-im/buildscripts) repository contains a set of scripts to build a fully static Ricochet on a clean Debian system. These are used to create the generic linux binary packages. +The [buildscripts](https://github.com/blueprint-freespeech/ricochet-refresh/tree/master/buildscripts) directory contains a set of scripts to build a fully static Ricochet Refresh on a clean Debian system. These are used to create the generic linux binary packages. #### Hardening -Ricochet will use aggressive compiler hardening flags if available. `qmake` will print the results of these tests on first run, or when run with `CONFIG+=recheck`. +Ricochet Refresh will use aggressive compiler hardening flags if available. `qmake` will print the results of these tests on first run, or when run with `CONFIG+=recheck`. To take full advantage of the sanitizer options, you may need to install `libasan` and `libubsan`. @@ -77,9 +105,9 @@ You can either load `ricochet.pro` in Qt Creator and build normally, or build co make ``` -You also need a `tor` binary in $PATH or inside the build's `ricochet.app/Contents/MacOS` folder. The easiest solution is to use `brew install tor`. If you copy the `tor` binary, you will need to keep it up to date. +You also need a `tor` binary in $PATH or inside the build's `ricochet refresh.app/Contents/MacOS` folder. The easiest solution is to use `brew install tor`. If you copy the `tor` binary, you will need to keep it up to date. -Normally, configuration will be stored in a `config.ricochet` folder, in the same location as `ricochet.app`. However, if the bundle is installed to `/Applications`, the system location `~/Library/Application Support/Ricochet` is used instead. You can force that behavior by adding `DEFINES+=RICOCHET_NO_PORTABLE` to the qmake command. +By default, configuration will be stored at `~/Library/Application Support/ricochet-refresh/` folder. This can be overriden by supplying a directory as first argument to the command-line. The `packaging/osx/release_osx.sh` script demonstrates how to build a redistributable app bundle. @@ -94,7 +122,7 @@ make ## Windows -Building for Windows is difficult. The process and scripts used for release builds are documented in the [buildscripts repository](https://github.com/ricochet-im/buildscripts/tree/master/mingw). +Building for Windows is difficult. The process and scripts used for release builds are documented in the [buildscripts directory](https://github.com/blueprint-freespeech/ricochet-refresh/tree/master/buildscripts). For development builds, you will want: * Visual Studio C++ or MinGW diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..1eb0b347 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at [ricochet-conduct@blueprintforfreespeech.net]( mailto:ricochet-conduct@blueprintforfreespeech.net). All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/CONTRIBUTE.md b/CONTRIBUTE.md new file mode 100644 index 00000000..741f65db --- /dev/null +++ b/CONTRIBUTE.md @@ -0,0 +1,63 @@ +# Contribution Guidelines + +We welcome contributions towards code, documentation, testing, and translation. +This guide should help explain the ways you can contribute and how to go about +it. + +## About the project + +If you are looking to contribute code, our project uses C++ with QT5. If you +aren't comfortable making a code contribution there are lots of other ways to +contribute, for example, improving documentation and testing across different +platforms. The best approach is to either comment on an issue you are interested +in contributing to, or create a new issue that describes the contribution you +wish to make. + +## How do I make a contribution? + +Never made an open source contribution before? Wondering how contributions work +in the in our project? Here's a quick rundown! + +- Find an issue that you are interested in addressing or a feature that you + would like to add. Create a new issue if necessary. +- Fork the repository associated with the issue to your local GitHub + organization. This means that you will have a copy of the repository under + your-GitHub-username/tego-core. +- Clone the repository to your local machine using `git clone + https://github.com/github-username/tego-core.git`. +- Create a new branch for your fix using `git checkout -b branch-name-here`. +- Make the appropriate changes for the issue you are trying to address or the + feature that you want to add. +- If you are new to Git or GitHub see + [https://guides.github.com/](https://guides.github.com/) for tutorials and + guides. +- Push your changes to your remote repository +- Submit a pull request to the upstream tego-core repository +- Provide a short description of the changes in the title of the pull request, + reference the issue or bug number as well. +- Provide more details of the changes in the pull description, in particular + highlight any current weaknesses or outstanding problems. If your code has + made changes to Crypto or protocol classes a detailed description of the + changes and justification will be required. Please try to make pull requests + for features separately - avoid bundling multiple issues or features into a + single pull request. This makes code review easier and more manageable. +- A maintainer will review the pull request and provide feedback where + appropriate. Please be patient, our maintainers are volunteers and only have a + limited amount of time to work on the project. +- Make changes to the pull request if the reviewing maintainer recommends them. + +## Where can I go for help? + +If you need help, you can ask questions via the issue that covers your +contribution. In future we will provide some additional communication channels +as we get them setup. + +## What does the Code of Conduct mean for me? + +Our Code of Conduct +[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg)](CODE_OF_CONDUCT.md) +means that you are responsible for treating everyone on the project with respect +and courtesy regardless of their identity. If you are the victim of any +inappropriate behavior or comments as described in our Code of Conduct, we are +here for you and will do the best to ensure that the person responsible is reprimanded +appropriately, per our code. diff --git a/README.md b/README.md index 86c9acf0..7eeb012f 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,44 @@ -### Anonymous metadata-resistant instant messaging that just works. -Ricochet is an experimental kind of instant messaging that **doesn't trust anyone** with your identity, your contact list, or your communications. +# Ricochet Refresh -* You can chat without exposing your identity (or IP address) to *anyone* -* Nobody can discover who your contacts are or when you talk (*metadata-free!*) -* There are no servers or operators that could be compromised, exposing your information. -* It's cross-platform and easy for non-technical users. +## R2: Ricochet Refresh +Ricochet Refresh is the new updated version of Ricochet, supported by Blueprint for Free Speech.. We are a non-government, not-for-profit organisation working to safeguard the freedom of expression for whistleblowers, activists, and everybody else, worldwide. To find out more, check out our profile, or head to [http://blueprintforfreespeech.net]. Blueprint was the original sponsor of Ricochet, written by developer J. Brooks. -![Screenshot](ricochetscreen.png) +Ricochet Refresh is currently available for OS X (10.12 or later), Linux, and Windows. Visit the [releases page](https://github.com/blueprint-freespeech/ricochet-refresh/releases) for the latest version and changelog. -### How it works -Ricochet is a peer-to-peer instant messaging system built on the Tor Network [hidden services](https://www.torproject.org/docs/hidden-services.html.en). Your login is your hidden service address, and contacts connect to you (not an intermediate server) through Tor. The rendezvous system makes it extremely hard for anyone to learn your identity from your address. +### What is Ricochet Refresh? +Ricochet Refresh is an instant messenger where no one knows your identity, who you're talking to, or what you're talking about. + * You can talk to whoever you want, without them knowing your identity (or IP address) + * No one can know who you're talking to, or when you're talking to them* (no metadata) + * You talk *directly* to your contact - there's no middleman server that could be compromised, putting you at risk. + * It's cross-platform, easy to install, and easy to use! + * It's open source, and open to contribution! -Ricochet is not affiliated with or endorsed by The Tor Project. +[//]: # (we should update this screenshot) +![Screenshot](ricochetscreen.png) -For more information, you can [read about Tor](https://www.torproject.org/about/overview.html.en) and [learn about Ricochet's design](https://github.com/ricochet-im/ricochet/blob/master/doc/design.md) or [protocol](https://github.com/ricochet-im/ricochet/blob/master/doc/protocol.md) (or the [old protocol](https://github.com/ricochet-im/ricochet/blob/master/doc/deprecated/protocol-1.0.txt)). Everything is [open-source](https://github.com/ricochet-im/ricochet/blob/master/LICENSE) and open to contribution. +### How does it work? +Ricochet Refresh uses the [Tor network](https://www.torproject.org/docs/hidden-services.html.en) to establish a peer-to-peer connection between you and your contact. Ricochet Refresh creates a service on the Tor network which contacts can connect to. Tor's rendezvous system makes it extremely difficult for anyone to learn the identity of a Tor user, including you. -### Experimental -This software is an experiment. Security and anonymity are difficult topics, and you should carefully evaluate your risks and exposure with any software. *Do not rely on Ricochet for your safety* unless you have more trust in my work than it deserves. That said, I believe it does more to try to protect your privacy than any similar software, and is the best chance you have of withholding your personal information. +Ricochet Refresh is not affiliated with, or endorsed by, The Tor Project. -### Downloads - -Ricochet is available for Windows, OS X (10.7 or later), and as a generic Linux binary package. Visit the [releases page](https://github.com/ricochet-im/ricochet/releases) for the latest version and changelog. +For more information about Tor, you can [read about Tor](https://www.torproject.org/about/overview.html.en) +For more information about Ricochet Refresh, you can: + * [Learn about its design](https://github.com/blueprint-freespeech/ricochet-refresh/blob/main/doc/design.md) + * [Learn about the protocol](https://github.com/blueprint-freespeech/ricochet-refresh/blob/main/doc/protocol.md) -All releases and signatures are also available at https://ricochet.im/releases/. +### Disclaimer +Security and anonymity are difficult, complicated, and deep topics. We strongly encourage you to evaluate your risks, threats, and exposure with any software. *Do not rely on Ricochet Refresh for your safety*. We believe Ricochet Refresh does a really good job at protecting your privacy and anonymity, however, we can not guarantee this protection. -Binaries are PGP signed by `9032 CAE4 CBFA 933A 5A21 45D5 FF97 C53F 183C 045D`. +### Downloads +Ricochet Refresh is currently available for OS X (10.12 or later), Linux, and Windows. Visit the [releases page](https://github.com/blueprint-freespeech/ricochet-refresh/releases) for the latest version and changelog. ### Building from source -See [BUILDING](https://github.com/ricochet-im/ricochet/blob/master/BUILDING.md) for Linux, OS X, and Windows build instructions. +See [BUILDING](https://github.com/blueprint-freespeech/ricochet-refresh/blob/master/BUILDING.md) for Linux, OS X, and Windows build instructions. ### Other -Bugs can be reported on the [issue tracker](https://github.com/ricochet-im/ricochet/issues). Translations can be contributed on [Transifex](https://www.transifex.com/projects/p/ricochet/). +Bugs can be reported on the [issue tracker](https://github.com/blueprint-freespeech/ricochet-refresh/issues). -You can contact me at `ricochet:rs7ce36jsj24ogfw` or `john.brooks@dereferenced.net`. +[//]: # (todo: update transifex link) +[//]: # (Translations can be contributed on [Transifex]\(https://www.transifex.com/projects/p/ricochet/\).) You should support [The Tor Project](https://www.torproject.org/donate/donate.html.en), [EFF](https://www.eff.org/), and [run a Tor relay](https://www.torproject.org/docs/tor-relay-debian.html.en). diff --git a/README.old.md b/README.old.md new file mode 100644 index 00000000..46d8a065 --- /dev/null +++ b/README.old.md @@ -0,0 +1,36 @@ +This is the [original README](https://github.com/ricochet-im/ricochet/blob/master/README.md), not by, or affiliated with, Blueprint for Free Speech + + +### Anonymous metadata-resistant instant messaging that just works. +Ricochet is an experimental kind of instant messaging that **doesn't trust anyone** with your identity, your contact list, or your communications. + +* You can chat without exposing your identity (or IP address) to *anyone* +* Nobody can discover who your contacts are or when you talk (*metadata-free!*) +* There are no servers or operators that could be compromised, exposing your information. +* It's cross-platform and easy for non-technical users. + +![Screenshot](ricochetscreen.png) + +### How it works +Ricochet is a peer-to-peer instant messaging system built on the Tor Network [hidden services](https://www.torproject.org/docs/hidden-services.html.en). Your login is your hidden service address, and contacts connect to you (not an intermediate server) through Tor. The rendezvous system makes it extremely hard for anyone to learn your identity from your address. + +Ricochet is not affiliated with or endorsed by The Tor Project. + +For more information, you can [read about Tor](https://www.torproject.org/about/overview.html.en) and [learn about Ricochet's design](https://github.com/ricochet-im/ricochet/blob/master/doc/design.md) or [protocol](https://github.com/ricochet-im/ricochet/blob/master/doc/protocol.md) (or the [old protocol](https://github.com/ricochet-im/ricochet/blob/master/doc/deprecated/protocol-1.0.txt)). Everything is [open-source](https://github.com/ricochet-im/ricochet/blob/master/LICENSE) and open to contribution. + +### Experimental +This software is an experiment. Security and anonymity are difficult topics, and you should carefully evaluate your risks and exposure with any software. *Do not rely on Ricochet for your safety* unless you have more trust in my work than it deserves. That said, I believe it does more to try to protect your privacy than any similar software, and is the best chance you have of withholding your personal information. + +### Downloads + +Ricochet is available for Windows, OS X (10.7 or later), and as a generic Linux binary package. Visit the [releases page](https://github.com/ricochet-im/ricochet/releases) for the latest version and changelog. + +All releases and signatures are also available at https://ricochet.im/releases/. + +### Building from source +See [BUILDING](https://github.com/blueprint-freespeech/ricochet-refresh/blob/master/BUILDING.md) for Linux, OS X, and Windows build instructions. + +### Other +Bugs can be reported on the [issue tracker](https://github.com/blueprint-freespeech/ricochet-refresh/issues). Translations can be contributed on [Transifex](https://www.transifex.com/projects/p/ricochet/). + +You should support [The Tor Project](https://www.torproject.org/donate/donate.html.en), [EFF](https://www.eff.org/), and [run a Tor relay](https://www.torproject.org/docs/tor-relay-debian.html.en). diff --git a/config.tests/mingw-64aslr/mingw-64aslr.pro b/config.tests/mingw-64aslr/mingw-64aslr.pro deleted file mode 100644 index 944b72ad..00000000 --- a/config.tests/mingw-64aslr/mingw-64aslr.pro +++ /dev/null @@ -1,3 +0,0 @@ -include(../../hardened.pri) -QMAKE_LFLAGS += $$HARDENED_MINGW_64ASLR_FLAGS -SOURCES += test.cpp diff --git a/config.tests/mingw-64aslr/test.cpp b/config.tests/mingw-64aslr/test.cpp deleted file mode 100644 index 76e81970..00000000 --- a/config.tests/mingw-64aslr/test.cpp +++ /dev/null @@ -1 +0,0 @@ -int main() { return 0; } diff --git a/config.tests/sanitize-ubsan-more/sanitize-ubsan-more.pro b/config.tests/sanitize-ubsan-more/sanitize-ubsan-more.pro deleted file mode 100644 index eb5faaf9..00000000 --- a/config.tests/sanitize-ubsan-more/sanitize-ubsan-more.pro +++ /dev/null @@ -1,4 +0,0 @@ -include(../../hardened.pri) -QMAKE_CXXFLAGS += $$HARDENED_SANITIZE_UBSAN_MORE_FLAGS -QMAKE_LFLAGS += $$HARDENED_SANITIZE_UBSAN_MORE_FLAGS -SOURCES += test.cpp diff --git a/config.tests/sanitize-ubsan-more/test.cpp b/config.tests/sanitize-ubsan-more/test.cpp deleted file mode 100644 index 76e81970..00000000 --- a/config.tests/sanitize-ubsan-more/test.cpp +++ /dev/null @@ -1 +0,0 @@ -int main() { return 0; } diff --git a/config.tests/sanitize-ubsan/sanitize-ubsan.pro b/config.tests/sanitize-ubsan/sanitize-ubsan.pro deleted file mode 100644 index 494a5ece..00000000 --- a/config.tests/sanitize-ubsan/sanitize-ubsan.pro +++ /dev/null @@ -1,4 +0,0 @@ -include(../../hardened.pri) -QMAKE_CXXFLAGS += $$HARDENED_SANITIZE_UBSAN_FLAGS -QMAKE_LFLAGS += $$HARDENED_SANITIZE_UBSAN_FLAGS -SOURCES += test.cpp diff --git a/config.tests/sanitize-ubsan/test.cpp b/config.tests/sanitize-ubsan/test.cpp deleted file mode 100644 index 76e81970..00000000 --- a/config.tests/sanitize-ubsan/test.cpp +++ /dev/null @@ -1 +0,0 @@ -int main() { return 0; } diff --git a/config.tests/sanitize/sanitize.pro b/config.tests/sanitize/sanitize.pro deleted file mode 100644 index 8a57c911..00000000 --- a/config.tests/sanitize/sanitize.pro +++ /dev/null @@ -1,4 +0,0 @@ -include(../../hardened.pri) -QMAKE_CXXFLAGS += $$HARDENED_SANITIZE_FLAGS -QMAKE_LFLAGS += $$HARDENED_SANITIZE_FLAGS -SOURCES += test.cpp diff --git a/config.tests/sanitize/test.cpp b/config.tests/sanitize/test.cpp deleted file mode 100644 index 76e81970..00000000 --- a/config.tests/sanitize/test.cpp +++ /dev/null @@ -1 +0,0 @@ -int main() { return 0; } diff --git a/config.tests/stack-protector-strong/stack-protector-strong.pro b/config.tests/stack-protector-strong/stack-protector-strong.pro deleted file mode 100644 index a937328e..00000000 --- a/config.tests/stack-protector-strong/stack-protector-strong.pro +++ /dev/null @@ -1,4 +0,0 @@ -include(../../hardened.pri) -QMAKE_CXXFLAGS += $$HARDENED_STACK_PROTECTOR_STRONG_FLAGS -QMAKE_LFLAGS += $$HARDENED_STACK_PROTECTOR_STRONG_FLAGS -SOURCES += test.cpp diff --git a/config.tests/stack-protector-strong/test.cpp b/config.tests/stack-protector-strong/test.cpp deleted file mode 100644 index 76e81970..00000000 --- a/config.tests/stack-protector-strong/test.cpp +++ /dev/null @@ -1 +0,0 @@ -int main() { return 0; } diff --git a/config.tests/stack-protector/stack-protector.pro b/config.tests/stack-protector/stack-protector.pro deleted file mode 100644 index 7c92274e..00000000 --- a/config.tests/stack-protector/stack-protector.pro +++ /dev/null @@ -1,4 +0,0 @@ -include(../../hardened.pri) -QMAKE_CXXFLAGS += $$HARDENED_STACK_PROTECTOR_FLAGS -QMAKE_LFLAGS += $$HARDENED_STACK_PROTECTOR_FLAGS -SOURCES += test.cpp diff --git a/config.tests/stack-protector/test.cpp b/config.tests/stack-protector/test.cpp deleted file mode 100644 index 76e81970..00000000 --- a/config.tests/stack-protector/test.cpp +++ /dev/null @@ -1 +0,0 @@ -int main() { return 0; } diff --git a/config.tests/vtable-verify/test.cpp b/config.tests/vtable-verify/test.cpp deleted file mode 100644 index 76e81970..00000000 --- a/config.tests/vtable-verify/test.cpp +++ /dev/null @@ -1 +0,0 @@ -int main() { return 0; } diff --git a/config.tests/vtable-verify/vtable-verify.pro b/config.tests/vtable-verify/vtable-verify.pro deleted file mode 100644 index b2af3695..00000000 --- a/config.tests/vtable-verify/vtable-verify.pro +++ /dev/null @@ -1,4 +0,0 @@ -include(../../hardened.pri) -QMAKE_CXXFLAGS += $$HARDENED_VTABLE_VERIFY_FLAGS -QMAKE_LFLAGS += $$HARDENED_VTABLE_VERIFY_FLAGS -SOURCES += test.cpp diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 00000000..8b7b6a22 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +ricochet-refresh (3.0.11) unstable; urgency=medium + + * Initial Release. + + -- Marco Simonelli Thu, 18 Nov 2021 17:54:15 +1100 diff --git a/debian/control b/debian/control new file mode 100644 index 00000000..8cfb795e --- /dev/null +++ b/debian/control @@ -0,0 +1,23 @@ +Source: ricochet-refresh +Maintainer: Marco Simonelli +Section: contrib/comm +Priority: optional +Build-Depends: debhelper-compat (= 12), cmake (>= 3.15), qtbase5-dev (>= 5.15), + qtdeclarative5-dev (>= 5.15), libfmt-dev (>= 6), libssl-dev (>= 1.1), + protobuf-compiler, libprotobuf-dev (>= 3) +Standards-Version: 4.4.1 +Homepage: https://github.com/blueprint-freespeech/ricochet-refresh.git + +Package: ricochet-refresh +Architecture: any +Section: contrib/comm +Priority: optional +Depends: ${shlibs:Depends}, ${misc:Depends}, + qml-module-qtquick-layouts, + qml-module-qtquick-controls, + qml-module-qtquick-dialogs, + tor +Description: Anonymous metadata-resistant instant messaging over tor + Ricochet refresh is an open source project to allow private and anonymous + instant messaging. It is metadata-resistant, anonymous, decentralised and + easy to use. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 00000000..1aaa5104 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,123 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: ricochet-refresh +Upstream-Contact: Blueprint for Free Speech +Source: https://github.com/blueprint-freespeech/ricochet-refresh.git + +Files: * +Copyright: 2019-2020, Blueprint For Free Speech + 2014-2016, John Brooks +License: Ricochet + Ricochet Refresh - https://ricochetrefresh.net/ + Copyright (C) 2019, Blueprint For Free Speech + Copyright (C) 2014-2016, John Brooks + . + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + . + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + . + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + . + * Neither the names of the copyright owners nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + . + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Files: src/extern/fmt/* +Copyright: 2012 - present, Victor Zverovich +License: fmt + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + . + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + . + --- Optional exception to the license --- + . + As an exception, if, as a result of your compiling your source code, portions + of this Software are embedded into a machine-executable object form of such + source code, you may redistribute such embedded portions in such object form + without including the above copyright and permission notices. + +Files: src/extern/tor/* +Copyright: 2001-2004, Roger Dingledine + 2004-2006, Roger Dingledine, Nick Mathewson + 2007-2019, The Tor Project, Inc. +License: tor + This file contains the license for Tor, + a free software project to provide anonymity on the Internet. + . + It also lists the licenses for other components used by Tor. + . + For more information about Tor, see https://www.torproject.org/. + . + If you got this file as a part of a larger bundle, + there may be other license terms that you should be aware of. + . + =============================================================================== + Tor is distributed under the "3-clause BSD" license, a commonly used + software license that means Tor is both free software and open source: + . + Copyright (c) 2001-2004, Roger Dingledine + Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson + Copyright (c) 2007-2019, The Tor Project, Inc. + . + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + . + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + . + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + . + * Neither the names of the copyright owners nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + . + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Files: src/extern/tor/src/ext/ed25519/donna/ed25519_tor.c +License: public-domain \ No newline at end of file diff --git a/debian/rules b/debian/rules new file mode 100755 index 00000000..a37a078a --- /dev/null +++ b/debian/rules @@ -0,0 +1,25 @@ +#!/usr/bin/make -f +export DH_VERBOSE = 1 + +%: + dh $@ --buildsystem=cmake + +override_dh_auto_configure: + mkdir -p build + echo "$(shell pwd)" + CMAKE_PREFIX_PATH="$(shell qmake -query QT_INSTALL_LIBS)/cmake/Qt5" \ + cmake -S ./src -B ./build -G"Unix Makefiles" \ + -DCMAKE_BUILD_TYPE=MinSizeRel \ + -DCMAKE_INSTALL_PREFIX="$(shell pwd)/debian/ricochet-refresh/" \ + -DRICOCHET_REFRESH_INSTALL_DESKTOP=ON + +override_dh_auto_build: + cmake --build ./build \ + -j$(subst parallel=,,$(filter parallel=%,$(subst $(COMMA), ,$(DEB_BUILD_OPTIONS)))) + +override_dh_auto_test: + # Testing requires catch2, which can't be installed via apt. Tests can be + # run prior to packaging regardless + +override_dh_auto_install: + cmake --build ./build --target install diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 00000000..89ae9db8 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (native) diff --git a/doc/manpage b/doc/manpage new file mode 100644 index 00000000..b7090b1e --- /dev/null +++ b/doc/manpage @@ -0,0 +1,28 @@ +.\" Manpage for Ricochet Refresh +.\" Submit a PR on the github repository for typos/errors (https://github.com/blueprint-freespeech/ricochet-refresh.git) +.Dd $Mdocdate$ +.Dt ricochet-refresh 1 +.Os +.Sh NAME +.Nm ricochet-refresh +.Nd Anonymous metadata-resistant instant messaging +.Sh SYNOPSIS +.Nm ricochet-refresh +.Op options +.Sh DESCRIPTION +.Nm +is an open source project to allow private and anonymous +instant messaging. It is metadata-resistant, anonymous and decentralised. +.Sh OPTIONS +.Nm +takes up to 1 argument, the ricochet folder path (relative to the current +working directory). If this folder does not exist, ricochet will create the +folder and inside that folder will create files containing tor keys and +contacts. If no argument is given, ricochet will create a config folder at +QStandardPaths::AppDataLocation (see +https://doc.qt.io/qt-5/qstandardpaths.html#StandardLocation-enum). +.Sh AUTHOR +.Nm +has been written by John Brooks and is +currently maintained by Blueprint For Free Speech + \ No newline at end of file diff --git a/doc/protocol.md b/doc/protocol.md index 94e31b74..c9cd76be 100644 --- a/doc/protocol.md +++ b/doc/protocol.md @@ -1,198 +1,214 @@ ## Overview -Ricochet is a peer-to-peer instant messaging system built on anonymity networks. This document -defines the communication protocol between two Ricochet instances, as carried out over a Tor hidden -service connection. +Ricochet is a peer-to-peer instant messaging system built on anonymity +networks. This document defines the communication protocol between two Ricochet +instances, as carried out over a Tor hidden service connection. The protocol is defined in three layers: -The **connection layer** describes the use of an anonymized TCP-style connection for peer-to-peer -communication. +The **connection layer** describes the use of an anonymized TCP-style +connection for peer-to-peer communication. -The **packet layer** separates the connection into a series of *packets* delivered to *channels*. -This allows multiplexing different operations on the same connection, and packetizes data for -channel-level parsing. +The **packet layer** separates the connection into a series of *packets* +delivered to *channels*. This allows multiplexing different operations on the +same connection, and packetizes data for channel-level parsing. -The **channel layer** parses and handles packets according to the *channel type* and the state of -that specific channel. +The **channel layer** parses and handles packets according to the *channel +type* and the state of that specific channel. ### Connections -> TODO: This is a brief explanation; we should reference a design/architecture document with more -> details. +> TODO: This is a brief explanation; we should reference a design/architecture +> document with more details. ##### Hidden services -Ricochet uses Tor [hidden services][rend-spec] as a transport; the reader should be familiar with -that architecture and the properties it provides. In particular: - - * The hostname is calculated from a hash of the server's public key, and serves to authenticate the - server without relying on a third party - * Connections are encrypted end-to-end, using the server's key and a DHE handshake to provide - forward secrecy - * Both ends of a connection are anonymous in that neither peer should be able to identify or locate - the other, and no relay should be able to connect an identity to the requests it makes - * Impersonating a server without its private key requires an 80-bit SHA1 collision using a valid - RSA key - -> TODO: We should explore additional cryptography on top of what Tor offers; see -> [issue 72](https://github.com/ricochet-im/ricochet/issues/72). +Ricochet uses Tor [hidden services][rend-spec] as a transport; the reader +should be familiar with that architecture and the properties it provides. In +particular: + + * The hostname is the base32 encoded form of the long term master identity + public key + * Connections are encrypted end-to-end using TLS/SSLv3, using the server's + key and a DHE handshake to provide forward secrecy + * Both ends of a connection are anonymous in that neither peer should be able + to identify or locate the other, and no relay should be able to connect an + identity to the requests it makes +> TODO: This doesn't phrase it well, emphasis is needed that impersonating a +> server *is* possible + * Impersonating a server without a valid private key is impossible ##### Usage -Each Ricochet instance publishes a hidden service, which serves as its identity and accepts -connections from contacts. When it first comes online, it attempts to connect to the addresses of -known contacts. If a connection is made, it is held open; a contact is considered online when there -is an open connection. Connections are made on port 9878. +Each Ricochet instance publishes a hidden service, which serves as its identity +and accepts connections from contacts. When it first comes online, it attempts +to connect to the addresses of known contacts. If a connection is made, it is +held open; a contact is considered online when there is an open connection. +Connections are made on port 9878. -> This solution isn't ideal; we'll be exploring better designs on top of hidden services to improve -> scalability and anonymity properties. +> This solution isn't ideal; we'll be exploring better designs on top of hidden +> services to improve scalability and anonymity properties. -Only one active connection is needed for a contact. Connections are fully bidirectional and all -behavior is equivalent regardless of which peer acts as server at the transport level. +Only one active connection is needed for a contact. Connections are fully +bidirectional and all behavior is equivalent regardless of which peer acts as +server at the transport level. -Ricochet does not use central servers; connections are made to services published directly by your -contacts with no intermediary. +Ricochet does not use central servers; connections are made to services +published directly by your contacts with no intermediary. -Keeping open connections to unknown peers poses a risk for various attacks, including resource -exhaustion. Clients must either authenticate or take other useful action (e.g. delivering a contact -request) quickly. The server side of the connection should expire unknown connections. +Keeping open connections to unknown peers poses a risk for various attacks, +including resource exhaustion. Clients must either authenticate or take other +useful action (e.g. delivering a contact request) quickly. The server side of +the connection should expire unknown connections. ### Channels -Channels divide up the connection to allow multiplexing, extensibility, and stateful behavior for -*packets*. +Channels divide up the connection to allow multiplexing, extensibility, and +stateful behavior for *packets*. -The **channel id** associates packets with an instance of a channel on the connection, which was -previously created by an *OpenChannel* message. +The **channel id** associates packets with an instance of a channel on the +connection, which was previously created by an *OpenChannel* message. -The **channel type** defines how packets are parsed and handled. Distinct features have separate -channel types; for example, `im.ricochet.chat` and `im.ricochet.file-transfer`. By convention, -these are in reverse-URI form. +The **channel type** defines how packets are parsed and handled. Distinct +features have separate channel types; for example, `im.ricochet.chat` and +`im.ricochet.file-transfer`. By convention, these are in reverse-URI form. -Channels exist within a connection. The channel ID is unique only within that connection, and all -channels are closed when the connection is lost. +Channels exist within a connection. The channel ID is unique only within that +connection, and all channels are closed when the connection is lost. -Channels must be explicitly created with an *OpenChannel* message. The recipient of that message -chooses to accept or reject the channel; for example, it may reject channel types it doesn't -support, or won't allow this peer to access. +Channels must be explicitly created with an *OpenChannel* message. The +recipient of that message chooses to accept or reject the channel; for example, +it may reject channel types it doesn't support, or won't allow this peer to +access. -Channel instances also provide a state for messages. For example, all operations associated with the -transfer of one file take place on the same channel, and a second file transfer would use a second -channel of the same type. +Channel instances also provide a state for messages. For example, all +operations associated with the transfer of one file take place on the same +channel, and a second file transfer would use a second channel of the same +type. -Both peers may send packets to the same channel. Depending on the channel type, messages may be -fully bidirectional or may be a command-response protocol. +Both peers may send packets to the same channel. Depending on the channel type, +messages may be fully bidirectional or may be a command-response protocol. -At the beginning of the connection, one channel exists automatically: the *control channel*. As a -special case, it always has a channel ID of `0`. The control channel provides functionality for -creating new channels and maintenance of the underlying connection. +At the beginning of the connection, one channel exists automatically: the +*control channel*. As a special case, it always has a channel ID of `0`. The +control channel provides functionality for creating new channels and +maintenance of the underlying connection. ### Authentication -Ricochet needs a variety of levels and types of authentication; known contacts might have a strong -proof of identity, while a request from a new person comes with a different proof and an anti-spam -"proof of work". Some features could allow unauthenticated use. +Ricochet needs a variety of levels and types of authentication; known contacts +might have a strong proof of identity, while a request from a new person comes +with a different proof and an anti-spam "proof of work". Some features could +allow unauthenticated use. -To support these scenarios, there is no pre-protocol authentication step. Peers add credentials to -their connection by opening and completing various types of authentication channels. +To support these scenarios, there is no pre-protocol authentication step. Peers +add credentials to their connection by opening and completing various types of +authentication channels. -The most common example is `im.ricochet.auth.hidden-service`: the peer creates a channel of this -type and carries out its protocol to prove that it has the private key for a hidden service name. -Afterwards, that peer can send a contact request, and the recipient is able to know the source of -that request. +The most common example is `im.ricochet.auth.hidden-service`: the peer creates +a channel of this type and carries out its protocol to prove that it has the +private key for a hidden service name. Afterwards, that peer can send a contact +request, and the recipient is able to know the source of that request. -Another hypothetical example is `example.hashcash`: the peer would complete a proof of work as -evidence that it doesn't intend to spam the recipient. +Another hypothetical example is `example.hashcash`: the peer would complete a +proof of work as evidence that it doesn't intend to spam the recipient. -These credentials are associated with the connection. For example, you may decide to not allow an -`im.ricochet.chat` channel unless the peer has completed `im.ricochet.auth.hidden-service` -authentication for a known contact's address. +These credentials are associated with the connection. For example, you may +decide to not allow an `im.ricochet.chat` channel unless the peer has completed +`im.ricochet.auth.hidden-service` authentication for a known contact's address. -The hidden service transport provides one special case: the server end of the connection is -authenticated equivalent to `im.ricochet.auth.hidden-service` at the beginning of the connection, -and must be given equivalent privileges. +The hidden service transport provides one special case: the server end of the +connection is authenticated equivalent to `im.ricochet.auth.hidden-service` at +the beginning of the connection, and must be given equivalent privileges. ### Conventions -Unless otherwise noted, these conventions and definitions are used for the protocol and this -document: +Unless otherwise noted, these conventions and definitions are used for the +protocol and this document: -* *Peer* refers to either Ricochet instance on a connection -* *Recipient* refers to the peer which received the message -* Channels encode data using [protocol buffers][protobuf], with one protobuf message per packet -* Unless the channel type specifies another mechanism, unknown/unparsable messages result in - closing the channel. -* Protocol behavior which appears malicious or strange may trigger consequences -* Strings are UTF-8 encoded and should be carefully validated and handled -* Any reply may be artificially delayed, but order must be preserved + * *Peer* refers to either Ricochet instance on a connection + * *Recipient* refers to the peer which received the message + * Channels encode data using [protocol buffers][protobuf], with one protobuf + message per packet + * Unless the channel type specifies another mechanism, unknown/unparsable + messages result in closing the channel. + * Protocol behavior which appears malicious or strange may trigger + consequences + * Strings are UTF-8 encoded and should be carefully validated and handled + * Any reply may be artificially delayed, but order must be preserved ## Specification ### Introduction and version negotiation -Immediately after establishing a connection, the client side must send an introduction message -identifying versions of the protocol that it is able to use. The server responds with one of -those versions, or an error indicating that no compatible version exists. +Immediately after establishing a connection, the client side must send an +introduction message identifying versions of the protocol that it is able to +use. The server responds with one of those versions, or an error indicating +that no compatible version exists. -This step exists to enable smoother protocol changes in the future, and for better compatibility -with old clients. +This step exists to enable smoother protocol changes in the future, and for +better compatibility with old clients. -The client begins the connection by sending the following raw sequence of bytes: +The client begins the connection by sending the following raw sequence of +bytes: ``` 0x49 0x4D -nVersions // One byte, number of supported protocol versions, must be at least 1 +nVersions // One byte, number of supported protocol versions must be at least 1 nVersions times: - version // One byte, protocol version number + version // One byte, protocol version number ``` -The total size is 3 plus the number of supported versions bytes. The number of supported versions must be at least 1. The server side of the connection -must respond with a single byte for the selected version number, or 0xFF if no suitable version -is found. +The total size is 3 plus the number of supported versions bytes. The number of +supported versions must be at least 1. The server side of the connection must +respond with a single byte for the selected version number, or 0xFF if no +suitable version is found. -This document describes protocol version 1. Known versions are: +This document describes protocol version 3. Known versions are: ``` -0 The Ricochet 1.0 protocol -1 This document +0 The Ricochet <= 1.0.x protocol +1 The Ricochet >= 1.1.x protocol +3 This document (The Ricochet 3.x.x protocol) ``` -If the negotiation is successful, the connection can be immediately used to begin exchanging messages -(the packet layer, below). +If the negotiation is successful, the connection can be immediately used to +begin exchanging messages (the packet layer, below). ### Packet layer The base layer on the connection is a trivial packet structure: ``` -uint16 size // Big endian, including the header bytes -uint16 channel // Big endian, channel identifier -bytes data // Content of the packet +uint16 size // Big endian, including the header bytes +uint16 channel // Big endian, channel identifier +bytes data // Content of the packet ``` -The connection reads and buffers data until it has a full packet, then looks up the channel -identifier within the list of open channels. If the channel is found, data is passed to it for -parsing and handling. +The connection reads and buffers data until it has a full packet, then looks up +the channel identifier within the list of open channels. If the channel is +found, data is passed to it for parsing and handling. -The only other functionality implemented at this layer is closing a channel. A channel is closed by -sending a packet to that channel with 0 bytes of data. When a packet is received for an unknown -channel, the recipient responds by closing that channel. +The only other functionality implemented at this layer is closing a channel. A +channel is closed by sending a packet to that channel with 0 bytes of data. +When a packet is received for an unknown channel, the recipient responds by +closing that channel. -Note that packets are limited to 65,535 bytes in size, including the 4-byte header. To avoid causing -latency on low throughput connections, channels should keep packets as small as possible. If a -channel type requires larger packets of data, it must define a way to reassemble them specific to -that channel type. +Note that packets are limited to 65,535 bytes in size, including the 4-byte +header. To avoid causing latency on low throughput connections, channels should +keep packets as small as possible. If a channel type requires larger packets of +data, it must define a way to reassemble them specific to that channel type. ### Control channel -The control channel is a special case: it is the only channel open from the beginning of a -connection, and it is always assigned the channel identifier `0`. If the control channel is closed, -the connection must also terminate. +The control channel is a special case: it is the only channel open from the +beginning of a connection, and it is always assigned the channel identifier +`0`. If the control channel is closed, the connection must also terminate. -The control channel contains methods used for maintenance of the connection and the creation of -other channels. It is a stateless series of protobuf-serialized `ControlMessage`, with one message -encoded per packet. Both peers on the connection may send all messages. +The control channel contains methods used for maintenance of the connection and +the creation of other channels. It is a stateless series of protobuf-serialized +`ControlMessage`, with one message encoded per packet. Both peers on the +connection may send all messages. ##### Packet ```protobuf @@ -206,7 +222,8 @@ message Packet { } ``` -All packets sent to the control channel must encode a *Packet*, with exactly one field. +All packets sent to the control channel must encode a *Packet*, with exactly +one field. ##### OpenChannel ```protobuf @@ -220,32 +237,37 @@ message OpenChannel { } ``` -Requests to open a channel of the type *channel_type*, using the identifier *channel_identifier* for -packets. Additional data may be added in extensions to this message. +Requests to open a channel of the type *channel_type*, using the identifier +*channel_identifier* for packets. Additional data specific to, and defined by, +the channel type may be added in extensions to this message. -The recipient of an OpenChannel message checks whether it supports the *channel_type*, if the -*channel_identifier* is valid and unassigned, and the validity of any extension data. The recipient -also checks whether this connection allows channels of this type; for example, if the peer is +The recipient of an OpenChannel message checks whether it supports the +*channel_type*, if the *channel_identifier* is valid and unassigned, and the +validity of any extension data. The recipient also checks whether this +connection allows channels of this type; for example, if the peer is sufficiently authenticated. -If the request is allowed, *channel_identifier* will be sent with packets destined for this channel -within this connection. It is also used to associate the *ChannelResult* message with this request. -There are several rules that must be followed when choosing or accepting a *channel_identifier*: +If the request is allowed, a *ChannelResult* message will be sent with the +*channel_identifier* field set to the requested *channel_identifier*, and the +*opened* field set to true. There are several rules that must be followed when +choosing or accepting a *channel_identifier*: -* The client side of a connection may only open odd-numbered channels -* The server side may only open even-numbered channels -* The identifier must fit within the range of uint16 -* The identifier must not be used by an open channel -* The identifier should increase for every OpenChannel message, wrapping if necessary. Identifiers - should not be re-used except after wrapping. + * The client side of a connection may only open odd-numbered channels + * The server side may only open even-numbered channels + * The identifier must fit within the range of uint16 + * The identifier must not be used by an open channel + * The identifier should increase for every OpenChannel message, wrapping if + necessary. Identifiers should not be re-used except after wrapping. -The even/odd restrictions on *channel_identifier* prevent peers from racing to open a channel with -the same id. Channels are tied to a specific connection, so there is no collision between connections. +The even/odd restrictions on *channel_identifier* prevent peers from racing to +open a channel with the same id. Channels are tied to a specific connection, so +there is no collision between connections. -By convention, channel types are in reverse URI format, e.g. `im.ricochet.chat`. +By convention, channel types are in reverse URI format, e.g. +`im.ricochet.chat`. -A *ChannelResult* message must always be generated in response. If the request is egregiously -invalid, the connection may be terminated instead. +A *ChannelResult* message must always be generated in response. If the request +is egregiously invalid, the connection may be terminated instead. ##### ChannelResult ```protobuf @@ -269,8 +291,9 @@ message ChannelResult { } ``` -Sent in response to an *OpenChannel* message, with the same *channel_identifier* value. If *opened* -is true, the channel is now ready to accept packets tagged with that identifier. +Sent in response to an *OpenChannel* message, with the same +*channel_identifier* value. If *opened* is true, the channel is now ready to +accept packets tagged with that identifier. ##### KeepAlive ```protobuf @@ -279,8 +302,8 @@ message KeepAlive { } ``` -A simple ping message. If *response_requested* is true, a *KeepAlive* message is generated in -response with *response_requested* as false. +A simple ping message. If *response_requested* is true, a *KeepAlive* message +is generated in response with *response_requested* as false. ##### EnableFeatures ```protobuf @@ -295,12 +318,13 @@ message FeaturesEnabled { } ``` -Simple feature negotiation. Either peer may send the *EnableFeatures* message with a list of -strings representing protocol changes or features. The recipient must respond with *FeaturesEnabled* -containing the subset of those strings it recognizes and has enabled. +Simple feature negotiation. Either peer may send the *EnableFeatures* message +with a list of strings representing protocol changes or features. The recipient +must respond with *FeaturesEnabled* containing the subset of those strings it +recognizes and has enabled. -No such feature strings are currently defined, and the current implementation should always respond -with an empty list. +No such feature strings are currently defined, and the current implementation +should always respond with an empty list. ### Chat channel @@ -312,13 +336,14 @@ with an empty list. | **Singleton** | Only one chat channel is created by each peer on the connection | | **Authentication** | Requires `im.ricochet.auth.hidden-service` as a known contact | -A chat channel allows the initiator (the peer who created the channel) to send messages, and receive -acknowledgement for those messages. The opposing peer should also create a chat channel to send its -own chat messages. Acknowledgement must be on the same channel as the original message. One peer may -not open more than one chat channel on the same connection. +A chat channel allows the initiator (the peer who created the channel) to send +messages, and receive acknowledgement for those messages. The opposing peer +should also create a chat channel to send its own chat messages. +Acknowledgement must be on the same channel as the original message. One peer +may not open more than one chat channel on the same connection. -Two chat channels (one per peer) are used to avoid ambiguity on which peer creates a chat channel, -or which channel would be used in a race situation. +Two chat channels (one per peer) are used to avoid ambiguity on which peer +creates a chat channel, or which channel would be used in a race situation. ##### Packet ```protobuf @@ -337,23 +362,27 @@ message ChatMessage { } ``` -A *message_id* of zero (or omitted) indicates that the recipient doesn't expect acknowledgement. - -If *message_id* is non-zero, the recipient should acknowledge receiving this message by sending -*ChatAcknowledge*. Unacknowledged messages may be re-sent with the same *message_id*, and the -recipient should drop duplicate messages with an identical non-zero *message_id*, after sending an +A *message_id* of zero (or omitted) indicates that the recipient doesn't expect acknowledgement. -Sometimes, messages may be delayed or potentially lost across connections over a short period of time. -In order to allow messages to be re-sent after a lost connection, clients should try to avoid choosing -message ids from a recent connection (with the same peer) at the start of a new connection. For -example, that can be done by randomizing the first message id for a channel. - -The *time_delta* field is a delta in seconds between when the message was composed and when it is being -transmitted. For messages that are sent immediately, it should be 0 or omitted. If a message was written -and couldn't be sent immediately (due to a connection failure, for example), the *time_delta* should be -an approximation of when it was composed. A positive value does not make any sense, as it would indicate -a message composed in the future. +If *message_id* is non-zero, the recipient should acknowledge receiving this +message by sending *ChatAcknowledge*. Unacknowledged messages may be re-sent +with the same *message_id*, and the recipient should drop duplicate messages +with an identical non-zero *message_id*, after sending an acknowledgement. + +Sometimes, messages may be delayed or potentially lost across connections over +a short period of time. In order to allow messages to be re-sent after a lost +connection, clients should try to avoid choosing message ids from a recent +connection (with the same peer) at the start of a new connection. For example, +that can be done by randomizing the first message id for a channel. + +The *time_delta* field is a delta in seconds between when the message was +composed and when it is being transmitted. For messages that are sent +immediately, it should be 0 or omitted. If a message was written and couldn't +be sent immediately (due to a connection failure, for example), the +*time_delta* should be an approximation of when it was composed. A positive +value does not make any sense, as it would indicate a message composed in the +future. ##### ChatAcknowledge ```protobuf @@ -369,6 +398,7 @@ The *accepted* parameter indicates whether or not the message is to be considered delivered to the client. If it is false, then the message delivery should be considered to have failed. + ### Contact request channel | Channel | Detail | @@ -379,15 +409,16 @@ should be considered to have failed. | **Singleton** | One instance created only by the client side of a connection | | **Authentication** | Requires `im.ricochet.auth.hidden-service` | -Contact requests are sent to introduce oneself to the recipient and ask for further contact, -including being put on the recipient's persistent contact list. +Contact requests are sent to introduce oneself to the recipient and ask for +further contact, including being put on the recipient's persistent contact +list. -The requesting client must have authenticated using `im.ricochet.auth.hidden-service` to prove -ownership of a hidden service name. +The requesting client must have authenticated using +`im.ricochet.auth.hidden-service` to prove ownership of a hidden service name. -The recipient isn't required to immediately respond to a request. If the request is approved, the -recipient may connect to the requesting client, and that is treated as implicitly accepting the -request. +The recipient isn't required to immediately respond to a request. If the +request is approved, the recipient may connect to the requesting client, and +that is treated as implicitly accepting the request. ##### OpenChannel ```protobuf @@ -400,11 +431,13 @@ extend Control.ChannelResult { } ``` -The OpenChannel message on a contact request channel must include the `contact_request` extension. A -successful ChannelResult must include the `response` extension. +The OpenChannel message on a contact request channel must include the +`contact_request` extension. A successful ChannelResult must include the +`response` extension. -If the response finishes the request, the channel will be closed immediately. Otherwise, the channel -remains open to wait for another *Response* message (e.g. going from Pending to Accepted). +If the response finishes the request, the channel will be closed immediately. +Otherwise, the channel remains open to wait for another *Response* message +(e.g. going from Pending to Accepted). ##### ContactRequest ```protobuf @@ -415,8 +448,9 @@ message ContactRequest { } ``` -Deliver a contact request, usually with a message and nickname attached. The "identity" of the -request is proven through `im.ricochet.auth.hidden-service` authentication. +Deliver a contact request, usually with a message and nickname attached. The +"identity" of the request is proven through `im.ricochet.auth.hidden-service` +authentication. The request is sent as an extension on the *OpenChannel* message. @@ -435,22 +469,25 @@ message Response { } ``` -Indicates the status of a contact request. The *Pending* status may be followed by another -*ContactRequestResponse* with a final status. All other statuses must be followed by closing the -channel, and may also close the connection. Closing the channel or the connection does not imply -having a response - for example, the recipient may decide to time out the connection while it is +Indicates the status of a contact request. The *Pending* status may be followed +by another *ContactRequestResponse* with a final status. All other statuses +must be followed by closing the channel, and may also close the connection. +Closing the channel or the connection does not imply having a response - for +example, the recipient may decide to time out the connection while it is waiting in the *Pending* state. -The initial *Response* is sent as an extension to the *ChannelResult* message when the channel is -opened. If that response is final, the channel is closed immediately after. Otherwise, the channel -remains open, and the only valid message is another *Response*. +The initial *Response* is sent as an extension to the *ChannelResult* message +when the channel is opened. If that response is final, the channel is closed +immediately after. Otherwise, the channel remains open, and the only valid +message is another *Response*. -If a request is *Rejected*, the requesting client must not send that request again, unless the user -has manually cancelled the previous request and made a new one. Recipients should automatically -reject excessive or abusive requests. +If a request is *Rejected*, the requesting client must not send that request +again, unless the user has manually cancelled the previous request and made a +new one. Recipients should automatically reject excessive or abusive requests. -If an *Error* occurs, the requesting client may only request again if it believes the error is -solved. Recipients should automatically reject requests after repetitive errors. +If an *Error* occurs, the requesting client may only request again if it +believes the error is solved. Recipients should automatically reject requests +after repetitive errors. ### AuthHiddenService @@ -462,13 +499,14 @@ solved. Recipients should automatically reject requests after repetitive errors. | **Singleton** | One instance created only by the client side of a connection | | **Authentication** | No prior authentication required | -The `im.ricochet.auth.hidden-service` channel is used to prove ownership of a hidden service name by -demonstrating ownership of a matching private key. This is used to authenticate as a known contact, -or to prove ownership of a service name before sending a contact request. +The `im.ricochet.auth.hidden-service` channel is used to prove ownership of a +hidden service name by demonstrating ownership of a matching private key. This +is used to authenticate as a known contact, or to prove ownership of a service +name before sending a contact request. -As a result of the transport, the server side of a hidden service connection is considered to have -automatically completed `im.ricochet.auth.hidden-service` authentication, and must be allowed -equivalent access. +As a result of the transport, the server side of a hidden service connection is +considered to have automatically completed `im.ricochet.auth.hidden-service` +authentication, and must be allowed equivalent access. ##### Packet ```protobuf @@ -486,15 +524,16 @@ message Packet { } ``` -The channel is opened by the peer who wishes to authenticate itself. The *OpenChannel* message -must contain a *client_cookie* of 16 bytes. A successful *ChannelResult* message must include -the *server_cookie* field, with a randomly generated value used to prevent replayed authentication. +The channel is opened by the peer who wishes to authenticate itself. The +*OpenChannel* message must contain a *client_cookie* of 16 bytes. A successful +*ChannelResult* message must include the *server_cookie* field, with a randomly +generated value used to prevent replayed authentication. ##### Proof ```protobuf message Proof { - optional bytes public_key = 1; // DER encoded RSA public key - optional bytes signature = 2; // RSA signature + optional bytes signature = 1; // ED25519-V3 signature + optional bytes service_id = 1; // v3 onion service id } ``` @@ -503,22 +542,22 @@ The proof is calculated as: ``` // + represents concatenation, and function is HMAC-SHA256(key, message) HMAC-SHA256(client_cookie + server_cookie, - client_hostname // base32-encoded client address, without .onion - + recipient_hostname // base32-encoded server address, without .onion + client_serviceid // base32-encoded client address, without .onion + + recipient_serviceid // base32-encoded server address, without .onion ) ``` -This proof is signed with the hidden service's private key using PKCS #1 v2.0 (as per OpenSSL -RSA_sign) to make *signature*. +This proof is signed with the hidden service's ED25519-v3 private key to make +*signature*. The recipient of this message must: -* Reject any message with a public_key field too large or not correctly formed to be a DER-encoded - 1024-bit RSA public key -* Reject any message with a signature field of an unexpected size -* Decode the public_key, and calculate its 'onion' address per [rend-spec][rend-spec] -* Build the proof message -* Verify that *signature* is a valid signature of the proof by *public_key* + * Reject any message with a service_id field of an unexpected size + * Reject any message with a signature field of an unexpected size + * Decode the public_key, and calculate its 'onion' address per + [rend-spec][rend-spec] + * Build the proof message + * Verify that *signature* is a valid signature of the proof by *public_key* ##### Result ```protobuf @@ -528,12 +567,139 @@ message Result { } ``` -If authentication is successful as a known contact, whose connection will be allowed to remain open -without any further purpose, the *is_known_contact* flag must be set as true. If this flag is not -set, the authenticating client should assume that it is not authorized (except e.g. to send a -contact request). +If authentication is successful as a known contact, whose connection will be +allowed to remain open without any further purpose, the *is_known_contact* flag +must be set as true. If this flag is not set, the authenticating client should +assume that it is not authorized (except e.g. to send a contact request). After sending *Result*, the channel should be closed. -[rend-spec]: https://gitweb.torproject.org/torspec.git/blob/HEAD:/rend-spec.txt +### File channel + +| Channel | Detail | +| ------------------ | ------ | +| **Channel type** | `im.ricochet.file-transfer` | +| **Purpose** | Sending files | +| **Direction** | One-way: Only initiator of the channel sends commands and file chunks, and recipient sends replies | +| **Singleton** | Only one file channel is created by each peer on the connection | +| **Authentication** | Requires `im.ricochet.auth.hidden-service` as a known contact | + +The file channel allows the initiator to send file transfer requests. The +recipient may respond either accepting or rejecting the request. Once the +initiator receives an accept response, they begin sending chunks. The recipient +must send an ACK message to the initiator after receiving a chunk before the +initiator will send the next chunk. This chunk, ack, chunk, ... pattern limits +throughput, but is necessary to avoid flooding the connection to the peer and +blocking chat messages from going through. In the future, a separate connection +on a separate socket should be created for the File channel to maximize +transfer speed. At any point, either the initiator or recipient may send a +message signaling the end of the transfer. + +##### Packet +```protobuf +message Packet { + optional FileHeader file_header = 1; + optional FileHeaderAck file_header_ack = 2; + optional FileHeaderResponse file_header_response = 3; + optional FileChunk file_chunk = 4; + optional FileChunkAck file_chunk_ack = 5; + optional FileTransferCompleteNotification file_transfer_complete_notification = 6; +} +``` + +All packets sent to the file channel must encode a *Packet*, with exactly one +field. Each packet contains a uint32 *file_id* field to identify which file +transfer the packet refers to. It is possible to have multiple file transfers +going on between the initiator and the recipient. + +##### FileHeader +```protobuf +message FileHeader { + optional uint32 file_id = 1; + optional uint64 file_size = 2; + optional string name = 3; + optional bytes file_hash = 4; +} +``` + +The initiator sends an initial FileHeader message to the recipient. It is +expected that the recipient will always send a FileHeaderAck response, if they +are able. The FileHeader message includes a per-peer unique identifier, and +some meta-data about the file the initiator wishes to send the recipient. + +##### FileHeaderAck +```protobuf +message FileHeaderAck { + optional uint32 file_id = 1; + optional bool accepted = 2 [default = false]; +} +``` + +The recipient always automatically sends a FileHeaderAck message in response to +a FileHeader message. The *accepted* field must be true to progress. + +##### FileHeaderResponse +```protobuf +message FileHeaderResponse { + optional uint32 file_id = 1; + optional int32 response = 2; +} +``` + +The *FileHeaderResponse* message is sent by the recipient after they send the +*FileHeaderAck* message to the initiator. This message indicates whether the +recipient wishes to proceed with the file transfer. The *response* field is an +int that corresponds with the *tego_file_transfer_response_t* enum defined in +tego.h; 0 for accept and 1 for reject. + +##### FileChunk +```protobuf +message FileChunk { + optional uint32 file_id = 1; + optional bytes chunk_data = 2; +} +``` + +Once the initiator has received a *FileHeaderResponse* message indicating the +recipient wishes to proceed with the file transfer, the initiator begins to +send *FileChunk* messages. The maximum number of bytes in each chunk is +currently defined as 63 kebibytes (63 * 1024 bytes). This constant is defined +in `libtego/source/protocol/FileChannel.h` After each *FileChunk* is sent by +the initiator, they must wait for the recipient to respond with a +*FileChunkAck* message before sending subsequent chunks. + +##### FileChunkAck +```protobuf +message FileChunkAck { + optional uint32 file_id = 1; + optional uint64 bytes_received = 2; +} +``` + +The *FileChunkAck* message is sent by the recipient immediately upon receiving +and writing a chunk to disk. + +##### FileTransferCompleteNotification +```protobuf +enum FileTransferResult { + Success = 0; + Failure = 1; + Cancelled = 2; +} + +message FileTransferCompleteNotification { + optional uint32 file_id = 1; + optional FileTransferResult result = 2; +} +``` + +The *FileTransferCompleteNotification* message may be sent by either the +initiator or the recipient at any time with a *result* value of *Failure* or +*Cancelled* to indicate that the file transfer is over. The recipient is +expected to send this message with a *result* of *Success* once all bytes have +been received. Once either party receives this message, they remove internal +state associated with the given transfer. Any subsequent messages referring to +a complete transfer are ignored. + +[rend-spec]: https://gitweb.torproject.org/torspec.git/plain/rend-spec-v3.txt [protobuf]: https://code.google.com/p/protobuf/ diff --git a/hardened.pri b/hardened.pri deleted file mode 100644 index 1bd3aaf5..00000000 --- a/hardened.pri +++ /dev/null @@ -1,47 +0,0 @@ -load(configure) -# Define common variables; these are used by config tests _and_ the actual build - -# Supported in gcc 4.8+ -HARDENED_SANITIZE_FLAGS = -fsanitize=address -# Supported in gcc 4.9+ -HARDENED_SANITIZE_UBSAN_FLAGS = -fsanitize=undefined -fsanitize=integer-divide-by-zero -fsanitize=bounds -fsanitize=alignment -fsanitize=float-divide-by-zero -fsanitize=float-cast-overflow -fno-sanitize-recover -# Supported in gcc 5.0+ -HARDENED_SANITIZE_UBSAN_MORE_FLAGS = -fsanitize=vptr -fsanitize=object-size -# vtable-verify requires some OS support; see https://bugzilla.novell.com/show_bug.cgi?id=877239 -HARDENED_VTABLE_VERIFY_FLAGS = -fvtable-verify=std - -HARDENED_STACK_PROTECTOR_STRONG_FLAGS = -fstack-protector-strong -HARDENED_STACK_PROTECTOR_FLAGS = -fstack-protector --param=ssp-buffer-size=4 - -HARDENED_MINGW_64ASLR_FLAGS = -Wl,--dynamicbase -Wl,--high-entropy-va - - -# Run tests and apply options where possible -CONFIG(hardened) { - # mingw is always PIC, and complains about the flag - !mingw:HARDEN_FLAGS = -fPIC - - CONFIG(debug,debug|release): qtCompileTest(sanitize):HARDEN_FLAGS += $$HARDENED_SANITIZE_FLAGS - qtCompileTest(sanitize-ubsan):HARDEN_FLAGS += $$HARDENED_SANITIZE_UBSAN_FLAGS - qtCompileTest(sanitize-ubsan-more):HARDEN_FLAGS += $$HARDENED_SANITIZE_UBSAN_MORE_FLAGS - qtCompileTest(vtable-verify):HARDEN_FLAGS += $$HARDENED_VTABLE_VERIFY_FLAGS - - qtCompileTest(stack-protector-strong) { - HARDEN_FLAGS += $$HARDENED_STACK_PROTECTOR_STRONG_FLAGS - } else { - qtCompileTest(stack-protector):HARDEN_FLAGS += $$HARDENED_STACK_PROTECTOR_FLAGS - } - - mingw { - qtCompileTest(mingw-64aslr):QMAKE_LFLAGS *= $$HARDENED_MINGW_64ASLR_FLAGS - QMAKE_LFLAGS *= -Wl,--nxcompat -Wl,--dynamicbase - } - - QMAKE_CXXFLAGS *= $$HARDEN_FLAGS - QMAKE_LFLAGS *= $$HARDEN_FLAGS - - # _FORTIFY_SOURCE requires -O, so only use on release builds - CONFIG(release,debug|release):QMAKE_CXXFLAGS *= -D_FORTIFY_SOURCE=2 - # Linux specific - unix:!macx:QMAKE_LFLAGS *= -pie -Wl,-z,relro,-z,now -} diff --git a/icons/Ricochet.icns b/icons/Ricochet.icns deleted file mode 100644 index b31d9e6b..00000000 Binary files a/icons/Ricochet.icns and /dev/null differ diff --git a/icons/icons.qrc b/icons/icons.qrc deleted file mode 100644 index b0548c12..00000000 --- a/icons/icons.qrc +++ /dev/null @@ -1,6 +0,0 @@ - - - ricochet.svg - ricochet_icons.ttf - - diff --git a/icons/ricochet.ico b/icons/ricochet.ico deleted file mode 100644 index 5c665b31..00000000 Binary files a/icons/ricochet.ico and /dev/null differ diff --git a/icons/ricochet.png b/icons/ricochet.png deleted file mode 100644 index c41ef508..00000000 Binary files a/icons/ricochet.png and /dev/null differ diff --git a/icons/ricochet.svg b/icons/ricochet.svg deleted file mode 100644 index 5c2e1376..00000000 --- a/icons/ricochet.svg +++ /dev/null @@ -1,1324 +0,0 @@ - - - -image/svg+xml \ No newline at end of file diff --git a/icons/ricochet_icons.json b/icons/ricochet_icons.json deleted file mode 100644 index ea47a53d..00000000 --- a/icons/ricochet_icons.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "ricochet_icons", - "css_prefix_text": "icon-", - "css_use_suffix": false, - "hinting": true, - "units_per_em": 1000, - "ascent": 850, - "glyphs": [ - { - "uid": "178053298e3e5b03551d754d4b9acd8b", - "css": "doc-inv", - "code": 59392, - "src": "fontawesome" - }, - { - "uid": "d10920db2e79c997c5e783279291970c", - "css": "dot-3", - "code": 59398, - "src": "entypo" - }, - { - "uid": "dbd39eb5a1d67beb54cfcb535e840e0f", - "css": "plus-4", - "code": 59408, - "src": "iconic" - }, - { - "uid": "26aba7edd46944209b4961670675a813", - "css": "cog-1", - "code": 59395, - "src": "websymbols" - } - ] -} \ No newline at end of file diff --git a/packaging/installer/SetupModern11.bmp b/packaging/installer/SetupModern11.bmp deleted file mode 100644 index 9ecefc73..00000000 Binary files a/packaging/installer/SetupModern11.bmp and /dev/null differ diff --git a/packaging/installer/building.txt b/packaging/installer/building.txt deleted file mode 100644 index 86c3e685..00000000 --- a/packaging/installer/building.txt +++ /dev/null @@ -1,10 +0,0 @@ -Building the installer requires Inno Setup; see http://www.jrsoftware.org/isinfo.php - -Place tor.exe and ricochet.exe in this directory. - -In console: - -PATH=%PATH%;C:\Path\To\Qt\bin -prepare.bat - -Then build from installer.iss to create the installer package. diff --git a/packaging/installer/installer.iss b/packaging/installer/installer.iss deleted file mode 100644 index 3bed79df..00000000 --- a/packaging/installer/installer.iss +++ /dev/null @@ -1,123 +0,0 @@ -#define ExeVersion GetFileVersion(AddBackslash(SourcePath) + "ricochet.exe") - -[Setup] -OutputBaseFilename=Ricochet -AppName=Ricochet -RestartIfNeededByRun=false -PrivilegesRequired=lowest -DefaultDirName={localappdata}\Ricochet\ -DisableProgramGroupPage=true -DisableDirPage=false -DisableReadyPage=false -DefaultGroupName=Ricochet -AppID={{B700250B-D3E2-407F-A534-8818EB8E3D93} -AppVersion={#ExeVersion} -UninstallDisplayName=Ricochet -Uninstallable=not IsPortableInstall -VersionInfoDescription=Ricochet -VersionInfoProductName=Ricochet -WizardImageFile=SetupModern11.bmp -ShowLanguageDialog=no -SetupIconFile=ricochet.ico -[Files] -Source: ricochet.exe; DestDir: {app}; DestName: ricochet.exe; Flags: replacesameversion -Source: LICENSE; DestDir: {app} -Source: tor.exe; DestDir: {app}; Flags: replacesameversion uninsrestartdelete -Source: Qt\*; DestDir: {app}; Flags: recursesubdirs -[Icons] -Name: {group}\Ricochet; Filename: {app}\ricochet.exe; WorkingDir: {app}; Comment: {cm:AppTitle}; Check: not IsPortableInstall -Name: {group}\{cm:UninstallShortcut}; Filename: {uninstallexe}; Check: not IsPortableInstall -[UninstallDelete] -Name: {app}\config; Type: filesandordirs -[Run] -Filename: {app}\ricochet.exe; WorkingDir: {app}; Description: {cm:RunShortcut}; Flags: postinstall nowait -[Languages] -Name: "bg"; MessagesFile: "translation\inno\Bulgarian.isl,translation\installer_bg.isl" -Name: "cs"; MessagesFile: "compiler:Languages\Czech.isl,translation\installer_cs.isl" -Name: "da"; MessagesFile: "compiler:Languages\Danish.isl,translation\installer_da.isl" -Name: "de"; MessagesFile: "compiler:Languages\German.isl,translation\installer_de.isl" -Name: "en"; MessagesFile: "compiler:Default.isl,translation\installer_en.isl" -Name: "es"; MessagesFile: "compiler:Languages\Spanish.isl,translation\installer_es.isl" -Name: "et_EE"; MessagesFile: "translation\inno\Estonian.isl,translation\installer_et_EE.isl" -Name: "fi"; MessagesFile: "compiler:Languages\Finnish.isl,translation\installer_fi.isl" -Name: "fr"; MessagesFile: "compiler:Languages\French.isl,translation\installer_fr.isl" -Name: "ja"; MessagesFile: "compiler:Languages\Japanese.isl,translation\installer_ja.isl" -Name: "it"; MessagesFile: "compiler:Languages\Italian.isl,translation\installer_it.isl" -Name: "it_IT"; MessagesFile: "compiler:Languages\Italian.isl,translation\installer_it_IT.isl" -Name: "nb"; MessagesFile: "compiler:Languages\Norwegian.isl,translation\installer_nb.isl" -Name: "nl_NL"; MessagesFile: "compiler:Languages\Dutch.isl,translation\installer_nl_NL.isl" -Name: "pt_BR"; MessagesFile: "compiler:Languages\BrazilianPortuguese.isl,translation\installer_pt_BR.isl" -Name: "pt_PT"; MessagesFile: "compiler:Languages\Portuguese.isl,translation\installer_pt_PT.isl" -Name: "ru"; MessagesFile: "compiler:Languages\Russian.isl,translation\installer_ru.isl" -Name: "sv"; MessagesFile: "translation\inno\Swedish.isl,translation\installer_sv.isl" -Name: "sq"; MessagesFile: "translation\inno\Albanian.isl,translation\installer_sq.isl" -Name: "tr"; MessagesFile: "compiler:Languages\Turkish.isl,translation\installer_tr.isl" -Name: "uk"; MessagesFile: "compiler:Languages\Ukrainian.isl,translation\installer_uk.isl" -Name: "pl"; MessagesFile: "compiler:Languages\Polish.isl,translation\installer_pl.isl" -Name: "sl"; MessagesFile: "compiler:Languages\Slovenian.isl,translation\installer_sl.isl" -Name: "zh"; MessagesFile: "translation\inno\ChineseSimplified.isl,translation\installer_zh.isl" -Name: "zh_HK"; MessagesFile: "translation\inno\ChineseSimplified.isl,translation\installer_zh_HK.isl" - -[Code] -// http://www.vincenzo.net/isxkb/index.php?title=Obtaining_the_application's_version -// http://www.vincenzo.net/isxkb/index.php?title=Uninstall_user_files - -var - PortablePage: TInputOptionWizardPage; - -procedure InitializeWizard; -begin - PortablePage := CreateInputOptionPage(wpWelcome, CustomMessage('PortableTitle'), CustomMessage('PortableDesc'), - CustomMessage('PortableText') + #13#13, True, False); - PortablePage.Add(CustomMessage('PortableOptInstall')); - PortablePage.Add(CustomMessage('PortableOptExtract')); - PortablePage.Values[0] := True; -end; - -function IsPortableInstall(): Boolean; -begin - Result := PortablePage.Values[1]; -end; - -procedure CurPageChanged(CurPageID: Integer); -var - s: String; - DefaultPortableDir: String; - DefaultInstallDir: String; -begin - if CurPageID = wpSelectDir then begin - DefaultPortableDir := ExtractFilePath(ExpandConstant('{srcexe}')) + 'Ricochet'; - DefaultInstallDir := ExpandConstant('{localappdata}') + '\Ricochet'; - - if IsPortableInstall() then begin - WizardForm.NextButton.Caption := CustomMessage('BtnExtract'); - WizardForm.SelectDirLabel.Caption := CustomMessage('ExtractDirText'); - WizardForm.PageDescriptionLabel.Caption := CustomMessage('ExtractDirDesc'); - if WizardForm.DirEdit.Text = DefaultInstallDir then - WizardForm.DirEdit.Text := DefaultPortableDir; - end else begin - WizardForm.NextButton.Caption := SetupMessage(msgButtonInstall); - s := SetupMessage(msgSelectDirLabel3); - StringChangeEx(s, '[name]', 'Ricochet', True); - WizardForm.SelectDirLabel.Caption := s; - s := SetupMessage(msgSelectDirDesc); - StringChangeEx(s, '[name]', 'Ricochet', True); - WizardForm.PageDescriptionLabel.Caption := s; - if WizardForm.DirEdit.Text = DefaultPortableDir then - WizardForm.DirEdit.Text := DefaultInstallDir; - end; - end; -end; - -function ShouldSkipPage(PageID: Integer): Boolean; -begin - if (PageID = wpSelectDir) and (not IsPortableInstall()) then - Result := True - else if (PageID = wpReady) and (IsPortableInstall()) then - Result := True - else - Result := False; -end; - - - diff --git a/packaging/installer/prepare.bat b/packaging/installer/prepare.bat deleted file mode 100644 index 47588c94..00000000 --- a/packaging/installer/prepare.bat +++ /dev/null @@ -1,8 +0,0 @@ -if exist "Qt\" rd /q /s Qt -windeployqt --qmldir ..\..\src\ui\qml --dir Qt ricochet.exe -if exist "Qt\qmltooling" rd /q /s Qt\qmltooling -copy /Y ..\..\icons\ricochet.ico ricochet.ico -copy /Y ..\..\LICENSE LICENSE -if not exist "translation\" mkdir translation -copy /Y ..\..\translation\installer_*.isl translation\ -xcopy /Y ..\..\translation\inno translation\inno\ diff --git a/packaging/linux-static/content/QtMultimedia/qmldir b/packaging/linux-static/content/QtMultimedia/qmldir deleted file mode 100644 index 1fcd488f..00000000 --- a/packaging/linux-static/content/QtMultimedia/qmldir +++ /dev/null @@ -1,4 +0,0 @@ -module QtMultimedia -plugin declarative_multimedia -classname Qt5MultimediaQuick -typeinfo plugins.qmltypes diff --git a/packaging/linux-static/content/QtQuick.2/qmldir b/packaging/linux-static/content/QtQuick.2/qmldir deleted file mode 100644 index 4a79c82e..00000000 --- a/packaging/linux-static/content/QtQuick.2/qmldir +++ /dev/null @@ -1,4 +0,0 @@ -module QtQuick -plugin qtquick2plugin -classname QtQuick2Plugin -typeinfo plugins.qmltypes diff --git a/packaging/linux-static/content/QtQuick/Controls/qmldir b/packaging/linux-static/content/QtQuick/Controls/qmldir deleted file mode 100644 index f51b0553..00000000 --- a/packaging/linux-static/content/QtQuick/Controls/qmldir +++ /dev/null @@ -1,4 +0,0 @@ -module QtQuick.Controls -plugin qtquickcontrolsplugin -classname QtQuickControlsPlugin -typeinfo plugins.qmltypes diff --git a/packaging/linux-static/content/QtQuick/Dialogs/qmldir b/packaging/linux-static/content/QtQuick/Dialogs/qmldir deleted file mode 100644 index b4ae1a05..00000000 --- a/packaging/linux-static/content/QtQuick/Dialogs/qmldir +++ /dev/null @@ -1,4 +0,0 @@ -module QtQuick.Dialogs -plugin dialogplugin -classname QtQuick2DialogsPlugin -typeinfo plugins.qmltypes diff --git a/packaging/linux-static/content/QtQuick/Layouts/qmldir b/packaging/linux-static/content/QtQuick/Layouts/qmldir deleted file mode 100644 index 6a260f02..00000000 --- a/packaging/linux-static/content/QtQuick/Layouts/qmldir +++ /dev/null @@ -1,4 +0,0 @@ -module QtQuick.Layouts -plugin qquicklayoutsplugin -classname QtQuickLayoutsPlugin -typeinfo plugins.qmltypes diff --git a/packaging/linux-static/content/QtQuick/PrivateWidgets/qmldir b/packaging/linux-static/content/QtQuick/PrivateWidgets/qmldir deleted file mode 100644 index da63c98e..00000000 --- a/packaging/linux-static/content/QtQuick/PrivateWidgets/qmldir +++ /dev/null @@ -1,4 +0,0 @@ -module QtQuick.PrivateWidgets -plugin widgetsplugin -classname QtQuick2PrivateWidgetsPlugin -typeinfo plugins.qmltypes diff --git a/packaging/linux-static/content/QtQuick/Window.2/qmldir b/packaging/linux-static/content/QtQuick/Window.2/qmldir deleted file mode 100644 index c9d1e5ac..00000000 --- a/packaging/linux-static/content/QtQuick/Window.2/qmldir +++ /dev/null @@ -1,4 +0,0 @@ -module QtQuick.Window -plugin windowplugin -classname QtQuick2WindowPlugin -typeinfo plugins.qmltypes diff --git a/packaging/linux-static/content/README b/packaging/linux-static/content/README deleted file mode 100644 index 0dd122e5..00000000 --- a/packaging/linux-static/content/README +++ /dev/null @@ -1,50 +0,0 @@ -Ricochet - https://ricochet.im/ - -This is a pre-built static distribution of Ricochet intended to run on most -Linux distributions. For source or other packages, visit the website above. - -You do not need to manually run or configure tor. An unmodified tor binary -is included with this package, and Ricochet will run it automatically, -similar to Tor Browser. There isn't any option to use a system tor daemon, -but Ricochet will use the tor binary from PATH if the bundled copy is removed. - -Once you run Ricochet, configuration will be stored in a 'config' directory -inside this folder. To upgrade, extract the new version to the same location. - -For more information, see: https://ricochet.im/ -Bug tracker: https://github.com/ricochet-im/ricochet/issues -Contact: - ricochet:rs7ce36jsj24ogfw - john.brooks@dereferenced.net - ------BEGIN PGP PUBLIC KEY BLOCK----- -Version: GnuPG - -mQENBFJ8UIcBCACm782I6UllTMl0X2ssFpTkIT/irLEWHJ2rnf2qFXUU3En9E+cR -jFpoutkjXyHPAcNedF5UHMG0lRkk3Xw7HIZlplMFLExR5UalF6j4nHBYMOw0h9pW -y5GxwcyuCal9R0RCGuSTi0pdQe6R7zj+ccay/njGgv3zA6M4b8XA5iklSfHvhMzQ -5lukjgyd54m8cVBvrHYOXJZQWZGOqnd+znIUm7bdCahAg5+29PEJ1S8NSMRofF0K -O7VBkv9niSZHGf4sFUxJgBzJosTsIoXzhpUGjPpfV246VN3UlQsACczCe+xW76P0 -Ou3r4gjjOM3qpUarzMosRR9oXO0xTIjoT9h1ABEBAAG0KkpvaG4gQnJvb2tzIDxq -b2huLmJyb29rc0BkZXJlZmVyZW5jZWQubmV0PokBPQQTAQoAJwUCUnxQhwIbAwUJ -B4YfgAULCQgHAwUVCgkICwUWAgMBAAIeAQIXgAAKCRD/l8U/GDwEXfh6B/9lbQGO -4jfY9XkiMkQZflKG+pvAktufE0tJ+oBF6JbNZQqXKl5hQZ9DdSrN+B4mnBW9N7Lw -8XAWP8Um4rXDH4ajrCp+rgMtvq2v32DSN5iZHLAjks3DF54q2plhBbH6Xq8u3wU8 -Brdm1kbZ+4qqRUmOEylfGG21Gzw4z1wfaw6bQaRbLu2eiKQOa/cvYYQUDlES+c4l -csawaiXOIDx/yZkHjUvDeC8GcPSIHQMUBnM2UOb4YXYPjhH7qPlOqhj3PLkQHLAp -IwMjN4hm646tTrzrAqU5puWFADYHAb1cKuNCOCkD5c2ihcHwsOtamoVcrRAKhOpc -zC+VJLPzFWj0EG6uuQENBFJ8UIcBCADRSpXjJjqWdCn3imbNcKKW2rwpruceucFd -9x8J6IfUhO1Ow3pTQtgY8nmPvley3fJ9MuAKUDvPZuEwoMCHgiFzrsfKfIKw4UGN -4xpC/CdyMDlxXtGTNRA5Mw+44V5DNeGL7cHRgebUmCjPr5sVIq4KHnNlhrKbirhU -KR9MC9oAxgln4zHi9br9KJP31GkClNYK26j2Hz3jOUwSF28fdNVJL1YRjH2vSXo7 -16rGcubmSe00+vXEdnhNcIGtIiiPWfBbQ3yTf02rw1JqNf/5oriUHucLGwrkK+Fs -MI1no59We55PmCnu4gZfNSrp+3srTRtdnO5MAWzzjC9cfhBsCQ9TABEBAAGJASUE -GAEKAA8FAlJ8UIcCGwwFCQeGH4AACgkQ/5fFPxg8BF0PjQgAloisPPYxnsobqpVY -H1ka6h+AJLm3WeaKnEfnNujZ1CTb5bjx8UzN6wUVBDRyhHLKY31vaPbgzZVvMz2/ -Xs0KyvuOVk3m9gB98W1dB4RAoVbTJvVe5VxRTAKouabcS/ezXOrOmECnxRFIaPg2 -mpBMzcGkOQ/Pivy8hLDXjlQlS4I/ItiAXfXzWVsMWpH3TaVX5DDT1P/rI56C/R3t -R6K8js9Gq2C3YNy+bPlVIUxwDH0g6A2DwieT+UaBfoIFPB9n0iXC3TJVyh8CrWrp -LxeIb1pmuX1z/Rvltbdt+k7nz927zqF8hR2BI+DXymBCZ0zmJET4ROg0LBpiPblE -gTkqzw== -=xleo ------END PGP PUBLIC KEY BLOCK----- diff --git a/packaging/linux-static/release.sh b/packaging/linux-static/release.sh deleted file mode 100755 index 081fae4c..00000000 --- a/packaging/linux-static/release.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/bash - -# Create and package a clean, release-mode build of the application -# Must be run from the git source directory. -# Create a .packagingrc file to define: -# TOR_BINARY Path to tor binary to copy into bundle. -# Should be built with --enable-static-libevent -# You can also set other environment, e.g. MAKEOPTS and PATH (for qmake) - -set -e - -if [ ! -d .git ] || [ ! -f ricochet.pro ]; then - echo "Must be run from source directory" - exit 1 -fi - -VERSION=`git describe --tags HEAD` - -. .packagingrc - -if [ -z "$TOR_BINARY" ] || [ ! -f "$TOR_BINARY" ]; then - echo "Missing TOR_BINARY: $TOR_BINARY" - exit 1 -fi - -rm -r build || true -mkdir build -cd build - -qmake CONFIG+=release ${QMAKEOPTS} .. -make -strip ricochet - -mkdir -p staging/ricochet -# Copy binaries to staging area -cp ricochet staging/ricochet/ -cp "$TOR_BINARY" staging/ricochet/ -# Copy extra files -cp -r ../packaging/linux-static/content/* staging/ricochet/ - -cd staging -tar cfj ricochet-${VERSION}-static.tar.bz2 ricochet -mv *.bz2 .. -cd .. - -echo "---------" - -tar fjt *.bz2 - -echo -echo "Output: ./build/ricochet-${VERSION}-static.tar.bz2" - diff --git a/packaging/osx/release_osx.sh b/packaging/osx/release_osx.sh deleted file mode 100755 index 91fcb158..00000000 --- a/packaging/osx/release_osx.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/bash - -# Create and package a clean, release-mode build of the application -# Must be run from the git source directory. -# Create a .packagingrc file to define: -# TOR_BINARY Path to tor binary to copy into bundle. -# Should be built with --enable-static-libevent -# You can also set other environment, e.g. MAKEOPTS and PATH (for qmake) - -# This script assumes Qt >= 5.4.0, for correct codesigning and macdeployqt behavior. - -set -e - -if [ ! -d .git ] || [ ! -f ricochet.pro ]; then - echo "Must be run from source directory" - exit 1 -fi - -. .packagingrc - -if [ -z "$TOR_BINARY" ] || [ ! -f "$TOR_BINARY" ]; then - echo "Missing TOR_BINARY: $TOR_BINARY" - exit 1 -fi - -rm -r build || true -mkdir build -cd build - -qmake CONFIG+=release .. -make - -cp "$TOR_BINARY" ricochet.app/Contents/MacOS/ -strip ricochet.app/Contents/MacOS/tor -strip ricochet.app/Contents/MacOS/ricochet - -macdeployqt ricochet.app -qmldir=../src/ui/qml/ -mv ricochet.app Ricochet.app - -# Code signing, if CODESIGN_ID is defined -if [ ! -z "$CODESIGN_ID" ]; then - codesign --verbose --sign "$CODESIGN_ID" --deep Ricochet.app -fi - -hdiutil create Ricochet.dmg -srcfolder Ricochet.app -format UDZO -volname Ricochet - -echo "---------" - -otool -L Ricochet.app/Contents/MacOS/ricochet -otool -L Ricochet.app/Contents/MacOS/tor - -codesign -vvvv -d Ricochet.app -spctl -vvvv --assess --type execute Ricochet.app - -echo -echo "Output: ./build/Ricochet.dmg" - diff --git a/packaging/rpm/ricochet.spec b/packaging/rpm/ricochet.spec deleted file mode 100644 index a6232a8b..00000000 --- a/packaging/rpm/ricochet.spec +++ /dev/null @@ -1,66 +0,0 @@ -Name: ricochet -Version: 1.1.0 -Release: 1%{?dist} -Summary: Anonymous peer-to-peer instant messaging - -License: BSD -URL: https://ricochet.im/ -Source0: https://ricochet.im/releases/%{version}/ricochet-%{version}-src.tar.bz2 - -BuildRequires: openssl-devel -BuildRequires: protobuf-compiler -BuildRequires: protobuf-devel -BuildRequires: qt5-qtbase-devel -BuildRequires: qt5-qtbase-gui -BuildRequires: qt5-qtdeclarative-devel -BuildRequires: qt5-qtmultimedia-devel -BuildRequires: qt5-qtquickcontrols -BuildRequires: qt5-qttools-devel -Requires: openssl-libs -Requires: protobuf -Requires: qt5-qtbase -Requires: qt5-qtbase-gui -Requires: qt5-qtdeclarative -Requires: qt5-qtquickcontrols -Requires: qt5-qtmultimedia -Requires: tor - -%description -Ricochet is an experiment with a different kind of instant messaging that doesn't trust anyone with your identity, your contact list, or your communications. - * You can chat without exposing your identity (or IP address) to anyone - * Nobody can discover who your contacts are or when you talk (metadata-free!) - * There are no servers to compromise or operators to intimidate for your information - * It's cross-platform and easy for non-technical users - - -%prep -%setup -q - - -%build -qmake-qt5 DEFINES+=RICOCHET_NO_PORTABLE CONFIG+=release -make -f Makefile.Release %{?_smp_mflags} - - -%install -make -f Makefile.Release install INSTALL_ROOT=%{buildroot} -install -m 0644 -D -p LICENSE %{buildroot}/%{_docdir}/%{name}/LICENSE -install -m 0644 -D -p AUTHORS.md %{buildroot}/%{_docdir}/%{name}/AUTHORS.md -install -m 0644 -D -p README.md %{buildroot}/%{_docdir}/%{name}/README.md - - -%files -/usr/bin/ricochet -/usr/share/applications/ricochet.desktop -/usr/share/icons/hicolor/48x48/apps/ricochet.png -/usr/share/icons/hicolor/scalable/apps/ricochet.svg -%docdir %{_docdir}/%{name} -%doc %{_docdir}/%{name}/LICENSE -%doc %{_docdir}/%{name}/AUTHORS.md -%doc %{_docdir}/%{name}/README.md - - - -%changelog -* Mon Jul 27 2015 Peter Ludikovsky 1.1.0-1 -- Initial RPM Package diff --git a/protobuf.pri b/protobuf.pri deleted file mode 100644 index 116626ad..00000000 --- a/protobuf.pri +++ /dev/null @@ -1,58 +0,0 @@ -# Qt qmake integration with Google Protocol Buffers compiler protoc -# -# To compile protocol buffers with qt qmake, specify PROTOS variable and -# include this file -# -# Based on: -# https://vilimpoc.org/blog/2013/06/09/using-google-protocol-buffers-with-qmake/ - -PROTOC = protoc - -unix { - PKG_CONFIG = $$pkgConfigExecutable() - - # All our dependency resolution depends on pkg-config. If it isn't - # available, the errors we will get subsequently are a lot more cryptic than - # this. - !system($$PKG_CONFIG --version 2>&1 > /dev/null) { - error("pkg-config executable is not available. please install it so I can find dependencies.") - } - - !contains(QT_CONFIG, no-pkg-config) { - CONFIG += link_pkgconfig - PKGCONFIG += protobuf - } else { - # Some SDK builds (e.g. OS X 5.4.1) are no-pkg-config, so try to hack the linker flags in. - QMAKE_LFLAGS += $$system($$PKG_CONFIG --libs protobuf) - } - - gcc|clang { - # Add -isystem for protobuf includes to suppress some loud compiler warnings in their headers - PROTOBUF_CFLAGS = $$system($$PKG_CONFIG --cflags protobuf) - PROTOBUF_CFLAGS ~= s/^(?!-I).*//g - PROTOBUF_CFLAGS ~= s/^-I(.*)/-isystem \\1/g - QMAKE_CXXFLAGS += $$PROTOBUF_CFLAGS - } -} - -win32 { - isEmpty(PROTOBUFDIR):error(You must pass PROTOBUFDIR=path/to/protobuf to qmake on this platform) - INCLUDEPATH += $${PROTOBUFDIR}/include - LIBS += -L$${PROTOBUFDIR}/lib -lprotobuf - contains(QMAKE_HOST.os,Windows):PROTOC = $${PROTOBUFDIR}/bin/protoc.exe -} - -protobuf_decl.name = protobuf headers -protobuf_decl.input = PROTOS -protobuf_decl.output = ${QMAKE_FILE_IN_PATH}/${QMAKE_FILE_BASE}.pb.h -protobuf_decl.commands = $$PROTOC --cpp_out=${QMAKE_FILE_IN_PATH} --proto_path=${QMAKE_FILE_IN_PATH} ${QMAKE_FILE_NAME} -protobuf_decl.variable_out = HEADERS -QMAKE_EXTRA_COMPILERS += protobuf_decl - -protobuf_impl.name = protobuf sources -protobuf_impl.input = PROTOS -protobuf_impl.output = ${QMAKE_FILE_IN_PATH}/${QMAKE_FILE_BASE}.pb.cc -protobuf_impl.depends = ${QMAKE_FILE_IN_PATH}/${QMAKE_FILE_BASE}.pb.h -protobuf_impl.commands = $$escape_expand(\n) -protobuf_impl.variable_out = SOURCES -QMAKE_EXTRA_COMPILERS += protobuf_impl diff --git a/ricochet.pro b/ricochet.pro deleted file mode 100644 index b939b096..00000000 --- a/ricochet.pro +++ /dev/null @@ -1,299 +0,0 @@ -# Ricochet - https://ricochet.im/ -# Copyright (C) 2014, John Brooks -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# -# * Neither the names of the copyright owners nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -lessThan(QT_MAJOR_VERSION,5)|lessThan(QT_MINOR_VERSION,1) { - error("Qt 5.1 or greater is required. You can build your own, or get the SDK at https://qt-project.org/downloads") -} - -TARGET = ricochet -TEMPLATE = app -QT += core gui network quick widgets -CONFIG += c++11 - -VERSION = 1.1.4 - -# Use CONFIG+=no-hardened to disable compiler hardening options -!CONFIG(no-hardened) { - CONFIG += hardened - include(hardened.pri) -} - -# Pass DEFINES+=RICOCHET_NO_PORTABLE for a system-wide installation - -CONFIG(release,debug|release):DEFINES += QT_NO_DEBUG_OUTPUT QT_NO_WARNING_OUTPUT - -contains(DEFINES, RICOCHET_NO_PORTABLE) { - unix:!macx { - target.path = /usr/bin - shortcut.path = /usr/share/applications - shortcut.files = src/ricochet.desktop - icon.path = /usr/share/icons/hicolor/48x48/apps/ - icon.files = icons/ricochet.png - scalable_icon.path = /usr/share/icons/hicolor/scalable/apps/ - scalable_icon.files = icons/ricochet.svg - INSTALLS += target shortcut icon scalable_icon - QMAKE_CLEAN += contrib/usr.bin.ricochet - contains(DEFINES, APPARMOR) { - apparmor_profile.extra = cp -f $${_PRO_FILE_PWD_}/contrib/usr.bin.ricochet-apparmor $${_PRO_FILE_PWD_}/contrib/usr.bin.ricochet - apparmor_profile.files = contrib/usr.bin.ricochet - QMAKE_CLEAN += contrib/usr.bin.ricochet - !isEmpty(APPARMORDIR) { - apparmor_profile.path = $${APPARMORDIR}/ - } else { - apparmor_profile.path = /etc/apparmor.d/ - } - INSTALLS += apparmor_profile - } - - exists(tor) { - message(Adding bundled Tor to installations) - bundletor.path = /usr/lib/ricochet/tor/ - bundletor.files = tor/* - INSTALLS += bundletor - DEFINES += BUNDLED_TOR_PATH=\\\"/usr/lib/ricochet/tor/\\\" - } - } -} - -macx { - CONFIG += bundle force_debug_plist - QT += macextras - - # Qt 5.4 introduces a bug that breaks QMAKE_INFO_PLIST when qmake has a relative path. - # Work around by copying Info.plist directly. - greaterThan(QT_MAJOR_VERSION,5)|greaterThan(QT_MINOR_VERSION,4) { - QMAKE_INFO_PLIST = src/Info.plist - } else:equals(QT_MAJOR_VERSION,5):lessThan(QT_MINOR_VERSION,4) { - QMAKE_INFO_PLIST = src/Info.plist - } else { - CONFIG += no_plist - QMAKE_POST_LINK += cp $${_PRO_FILE_PWD_}/src/Info.plist $${OUT_PWD}/$${TARGET}.app/Contents/; - } - - exists(tor) { - # Copy the entire tor/ directory, which should contain tor/tor (the binary itself) - QMAKE_POST_LINK += cp -R $${_PRO_FILE_PWD_}/tor $${OUT_PWD}/$${TARGET}.app/Contents/MacOS/; - } - - icons.files = icons/Ricochet.icns - icons.path = Contents/Resources/ - QMAKE_BUNDLE_DATA += icons -} - -CONFIG += debug_and_release - -# Create a pdb for release builds as well, to enable debugging -win32-msvc2008|win32-msvc2010 { - QMAKE_CXXFLAGS_RELEASE += /Zi - QMAKE_LFLAGS_RELEASE += /DEBUG /OPT:REF,ICF -} - -INCLUDEPATH += src - -win32|mac { - # For mac, this is necessary because homebrew does not link openssl .pc to - # /usr/local/lib/pkgconfig (presumably because it used to be a system - # package). - # - # Unfortunately, it is no longer really a system package, and we really - # need to know where it is. - isEmpty(OPENSSLDIR): error(You must pass OPENSSLDIR=path/to/openssl to qmake on this platform) -} - -unix { - !isEmpty(OPENSSLDIR) { - INCLUDEPATH += $${OPENSSLDIR}/include - LIBS += -L$${OPENSSLDIR}/lib -lcrypto - } else { - CONFIG += link_pkgconfig - PKGCONFIG += libcrypto - } -} -win32 { - INCLUDEPATH += $${OPENSSLDIR}/include - - win32-g++ { - LIBS += -L$${OPENSSLDIR}/lib -lcrypto - } else { - LIBS += -L$${OPENSSLDIR}/lib -llibeay32 - } - - # required by openssl - LIBS += -luser32 -lgdi32 -ladvapi32 -} - -# Exclude unneeded plugins from static builds -QTPLUGIN.playlistformats = - -QTPLUGIN.imageformats = - -QTPLUGIN.printsupport = - -QTPLUGIN.mediaservice = - -# Include Linux input plugins, which are missing by default, to provide complex input support. See issue #60. -unix:!macx:QTPLUGIN.platforminputcontexts = composeplatforminputcontextplugin ibusplatforminputcontextplugin - -DEFINES += QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII - -SOURCES += src/main.cpp \ - src/ui/MainWindow.cpp \ - src/ui/ContactsModel.cpp \ - src/tor/TorControl.cpp \ - src/tor/TorControlSocket.cpp \ - src/tor/TorControlCommand.cpp \ - src/tor/ProtocolInfoCommand.cpp \ - src/tor/AuthenticateCommand.cpp \ - src/tor/SetConfCommand.cpp \ - src/tor/AddOnionCommand.cpp \ - src/utils/StringUtil.cpp \ - src/core/ContactsManager.cpp \ - src/core/ContactUser.cpp \ - src/tor/GetConfCommand.cpp \ - src/tor/HiddenService.cpp \ - src/utils/CryptoKey.cpp \ - src/utils/SecureRNG.cpp \ - src/core/OutgoingContactRequest.cpp \ - src/core/IncomingRequestManager.cpp \ - src/core/ContactIDValidator.cpp \ - src/core/UserIdentity.cpp \ - src/core/IdentityManager.cpp \ - src/core/ConversationModel.cpp \ - src/tor/TorProcess.cpp \ - src/tor/TorManager.cpp \ - src/tor/TorSocket.cpp \ - src/ui/LinkedText.cpp \ - src/utils/Settings.cpp \ - src/utils/PendingOperation.cpp \ - src/ui/LanguagesModel.cpp - -HEADERS += src/ui/MainWindow.h \ - src/ui/ContactsModel.h \ - src/tor/TorControl.h \ - src/tor/TorControlSocket.h \ - src/tor/TorControlCommand.h \ - src/tor/ProtocolInfoCommand.h \ - src/tor/AuthenticateCommand.h \ - src/tor/SetConfCommand.h \ - src/tor/AddOnionCommand.h \ - src/utils/StringUtil.h \ - src/core/ContactsManager.h \ - src/core/ContactUser.h \ - src/tor/GetConfCommand.h \ - src/tor/HiddenService.h \ - src/utils/CryptoKey.h \ - src/utils/SecureRNG.h \ - src/core/OutgoingContactRequest.h \ - src/core/IncomingRequestManager.h \ - src/core/ContactIDValidator.h \ - src/core/UserIdentity.h \ - src/core/IdentityManager.h \ - src/core/ConversationModel.h \ - src/tor/TorProcess.h \ - src/tor/TorProcess_p.h \ - src/tor/TorManager.h \ - src/tor/TorSocket.h \ - src/ui/LinkedText.h \ - src/utils/Settings.h \ - src/utils/PendingOperation.h \ - src/ui/LanguagesModel.h - -SOURCES += src/protocol/Channel.cpp \ - src/protocol/ControlChannel.cpp \ - src/protocol/Connection.cpp \ - src/protocol/OutboundConnector.cpp \ - src/protocol/AuthHiddenServiceChannel.cpp \ - src/protocol/ChatChannel.cpp \ - src/protocol/ContactRequestChannel.cpp - -HEADERS += src/protocol/Channel.h \ - src/protocol/Channel_p.h \ - src/protocol/ControlChannel.h \ - src/protocol/Connection.h \ - src/protocol/Connection_p.h \ - src/protocol/OutboundConnector.h \ - src/protocol/AuthHiddenServiceChannel.h \ - src/protocol/ChatChannel.h \ - src/protocol/ContactRequestChannel.h - -include(protobuf.pri) -PROTOS += src/protocol/ControlChannel.proto \ - src/protocol/AuthHiddenService.proto \ - src/protocol/ChatChannel.proto \ - src/protocol/ContactRequestChannel.proto - -# QML -RESOURCES += src/ui/qml/qml.qrc \ - icons/icons.qrc \ - sounds/sounds.qrc - -win32:RC_ICONS = icons/ricochet.ico -OTHER_FILES += src/ui/qml/* -lupdate_only { - SOURCES += src/ui/qml/*.qml -} - -# Translations -TRANSLATIONS += \ - ricochet_en \ - ricochet_it \ - ricochet_es \ - ricochet_da \ - ricochet_pl \ - ricochet_pt_BR \ - ricochet_de \ - ricochet_bg \ - ricochet_cs \ - ricochet_fi \ - ricochet_fr \ - ricochet_ru \ - ricochet_uk \ - ricochet_tr \ - ricochet_nl_NL \ - ricochet_fil_PH \ - ricochet_sv \ - ricochet_he \ - ricochet_sl \ - ricochet_zh \ - ricochet_et_EE \ - ricochet_it_IT \ - ricochet_nb \ - ricochet_pt_PT \ - ricochet_sq \ - ricochet_zh_HK \ - ricochet_ja - -# Only build translations when creating the primary makefile. -!build_pass: { - contains(QMAKE_HOST.os,Windows):QMAKE_LRELEASE = $$[QT_INSTALL_BINS]/lrelease.exe - else:QMAKE_LRELEASE = $$[QT_INSTALL_BINS]/lrelease - for (translation, TRANSLATIONS) { - system($$QMAKE_LRELEASE translation/$${translation}.ts -qm translation/$${translation}.qm) - } -} - -RESOURCES += translation/embedded.qrc diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 00000000..d9e614ac --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,89 @@ +# Ricochet Refresh - https://ricochetrefresh.net/ +# Copyright (C) 2021, Blueprint For Free Speech +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# +# * Neither the names of the copyright owners nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +cmake_minimum_required(VERSION 3.16) + +project(tego) + +if (ENABLE_LIBTEGO_TESTS) + # this needs to go in the root source folder because CTest expects the + # config in the root build folder + enable_testing() +endif () + +get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH) +get_filename_component(outdir "${CMAKE_BINARY_DIR}" REALPATH) + +# no in source builds +if ("${srcdir}" STREQUAL "${outdir}") + message( + FATAL_ERROR + "In-source builds are not allowed. Please create a seperate build directory and run cmake from there.") +endif () + +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake) + +# Disable defining NDEBUG, since tor requires it to build, and we include +# headers from tor +if (CMAKE_CXX_COMPILER_ID MATCHES ".*Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + string(REPLACE "-DNDEBUG" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") + string(REPLACE "-DNDEBUG" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") + string(REPLACE "-DNDEBUG" "" CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL}") + string(REPLACE "-DNDEBUG" "" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}") + string(REPLACE "-DNDEBUG" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") + string(REPLACE "-DNDEBUG" "" CMAKE_C_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_MINSIZEREL}") +elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + string(REPLACE "/DNDEBUG" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") + string(REPLACE "/DNDEBUG" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") + string(REPLACE "/DNDEBUG" "" CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL}") + string(REPLACE "/DNDEBUG" "" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}") + string(REPLACE "/DNDEBUG" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") + string(REPLACE "/DNDEBUG" "" CMAKE_C_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_MINSIZEREL}") +else () + message(WARNING "Could not determine compiler, this may not build on non debug builds!") +endif () + +# openssl configuration. this propogates to all subprojects +set(OPENSSL_USE_STATIC_LIBS OFF CACHE BOOL "Use statically-linked OpenSSL") +set(OPENSSL_ROOT_DIR "" CACHE STRING "Path to custom OpenSSL installation") + +# qt configuration +set(STATIC_QT OFF CACHE BOOL "Enable if using a static build of QT" ) +set(STATIC_QT_ROOT_DIR "" CACHE STRING "Path to static build of QT (ignored if STATIC_QT is set to OFF)") + +option(USE_SUBMODULE_FMT "Use the version of fmt included as a submodule instead of one potentially installed to the system" ON) +if (USE_SUBMODULE_FMT) + add_subdirectory(extern/fmt) +endif () + +add_subdirectory(libtego) +add_subdirectory(libtego_ui) +add_subdirectory(ricochet-refresh) diff --git a/LICENSE b/src/LICENSE similarity index 97% rename from LICENSE rename to src/LICENSE index ca58b029..51c26e49 100644 --- a/LICENSE +++ b/src/LICENSE @@ -1,4 +1,5 @@ - Ricochet - https://ricochet.im/ + Ricochet Refresh - https://ricochetrefresh.net/ +Copyright (C) 2019, Blueprint For Free Speech Copyright (C) 2014-2016, John Brooks Redistribution and use in source and binary forms, with or without @@ -32,14 +33,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ------------------------------------------------------------------------------- Tor is a registered trademark of The Tor Project, Inc. -Ricochet is not in any way affiliated with or endorsed by The Tor Project. +Ricochet Refresh is not in any way affiliated with or endorsed by The Tor Project. For more information about Tor, see https://www.torproject.org/. Tor is distributed under this license: Copyright (c) 2001-2004, Roger Dingledine Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson -Copyright (c) 2007-2010, The Tor Project, Inc. +Copyright (c) 2007-2019, The Tor Project, Inc. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -114,8 +115,6 @@ details. ------------------------------------------------------------------------------- -Ricochet "R" icon by Lawrence Eastland, CC-BY-SA 4.0. See icons/LICENSE. - ------------------------------------------------------------------------------- /* ==================================================================== diff --git a/src/cmake/compiler/san/clang-san.cmake b/src/cmake/compiler/san/clang-san.cmake new file mode 100644 index 00000000..6f5817b9 --- /dev/null +++ b/src/cmake/compiler/san/clang-san.cmake @@ -0,0 +1,50 @@ +# based off https://github.com/cpp-best-practices/cpp_starter_project/blob/main/cmake/Sanitizers.cmake + +function (clang_setup_sanitizers target) + option(ENABLE_COVERAGE "Enable GCC/Clang coverage reporting" OFF) + option(ENABLE_SANITIZER_ADDRESS "Enable address sanitizer" OFF) + option(ENABLE_SANITIZER_LEAK "Enable leak sanitizer" OFF) + option(ENABLE_SANITIZER_UNDEFINED_BEHAVIOR "Enable undefined behavior sanitizer" OFF) + option(ENABLE_SANITIZER_THREAD "Enable thread sanitizer" OFF) + + if (ENABLE_COVERAGE) + target_compile_options(${target} INTERFACE --coverage -O0 -g) + target_link_libraries(${target} INTERFACE --coverage) + endif () + + set(SANITIZERS "") + + if (ENABLE_SANITIZER_ADDRESS) + list(APPEND SANITIZERS "address") + endif () + + if (ENABLE_SANITIZER_LEAK) + list(APPEND SANITIZERS "leak") + endif () + + if (ENABLE_SANITIZER_UNDEFINED_BEHAVIOR) + list(APPEND SANITIZERS "undefined") + endif () + + if (ENABLE_SANITIZER_THREAD) + if ("address" IN_LIST SANITIZERS OR "leak" IN_LIST SANITIZERS) + message(WARNING "Thread sanitizer does not work with Address and Leak sanitizer enabled") + else () + list(APPEND SANITIZERS "thread") + endif () + endif () + + # create the sanitizer flags in the form -fsanitize=,,... + list( + JOIN + SANITIZERS + "," + SAN_FLAGS) + if (NOT + ${SAN_FLAGS} + STREQUAL + "") + target_compile_options(${target} INTERFACE -fsanitize=${SAN_FLAGS}) + target_link_options(${target} INTERFACE -fsanitize=${SAN_FLAGS}) + endif () +endfunction () diff --git a/src/cmake/compiler/san/gcc-san.cmake b/src/cmake/compiler/san/gcc-san.cmake new file mode 100644 index 00000000..073f0a49 --- /dev/null +++ b/src/cmake/compiler/san/gcc-san.cmake @@ -0,0 +1,6 @@ +# the only sanitizer that clang supports that GCC doesn't is memorysan, which we don't support anyway +include(clang-san) + +function (gcc_setup_sanitizers target) + clang_setup_sanitizers(${target}) +endfunction () diff --git a/src/cmake/compiler/san/msvc-san.cmake b/src/cmake/compiler/san/msvc-san.cmake new file mode 100644 index 00000000..4d1060f4 --- /dev/null +++ b/src/cmake/compiler/san/msvc-san.cmake @@ -0,0 +1,25 @@ +# based off https://github.com/cpp-best-practices/cpp_starter_project/blob/main/cmake/Sanitizers.cmake + +function (msvc_setup_sanitizers target) + option(ENABLE_SANITIZER_ADDRESS "Enable address sanitizer" OFF) + + set(SANITIZERS "") + + if (ENABLE_SANITIZER_ADDRESS) + list(APPEND SANITIZERS "address") + endif () + + # create the sanitizer flags in the form /fsanitize=,,... + list( + JOIN + SANITIZERS + "," + SAN_FLAGS) + if (NOT + ${SAN_FLAGS} + STREQUAL + "") + target_compile_options(${target} INTERFACE /fsanitize=${SAN_FLAGS}) + target_link_options(${target} INTERFACE /fsanitize=${SAN_FLAGS}) + endif () +endfunction () diff --git a/src/cmake/compiler/static/clang-static.cmake b/src/cmake/compiler/static/clang-static.cmake new file mode 100644 index 00000000..cc99f1fc --- /dev/null +++ b/src/cmake/compiler/static/clang-static.cmake @@ -0,0 +1,4 @@ +# Nothing extra we want to statically link for now, but if the need ever comes +# about, add it here +function (clang_setup_static_build target) +endfunction () \ No newline at end of file diff --git a/src/cmake/compiler/static/gcc-static.cmake b/src/cmake/compiler/static/gcc-static.cmake new file mode 100644 index 00000000..b43cb156 --- /dev/null +++ b/src/cmake/compiler/static/gcc-static.cmake @@ -0,0 +1,4 @@ +# Nothing extra we want to statically link for now, but if the need ever comes +# about, add it here +function (gcc_setup_static_build target) +endfunction () \ No newline at end of file diff --git a/src/cmake/compiler/static/mingw-static.cmake b/src/cmake/compiler/static/mingw-static.cmake new file mode 100644 index 00000000..498e62a1 --- /dev/null +++ b/src/cmake/compiler/static/mingw-static.cmake @@ -0,0 +1,3 @@ +function (mingw_setup_static_build target) + target_link_options(${target} PRIVATE "-static") +endfunction () \ No newline at end of file diff --git a/src/cmake/compiler/static/msvc-static.cmake b/src/cmake/compiler/static/msvc-static.cmake new file mode 100644 index 00000000..9836a5f6 --- /dev/null +++ b/src/cmake/compiler/static/msvc-static.cmake @@ -0,0 +1,3 @@ +# TODO +function (msvc_setup_static_build target) +endfunction () \ No newline at end of file diff --git a/src/cmake/compiler/warnings/clang-warnings.cmake b/src/cmake/compiler/warnings/clang-warnings.cmake new file mode 100644 index 00000000..782c796f --- /dev/null +++ b/src/cmake/compiler/warnings/clang-warnings.cmake @@ -0,0 +1,43 @@ +# based off https://github.com/cpp-best-practices/cpp_starter_project/blob/main/cmake/CompilerWarnings.cmake + +set(CXX_WARNINGS + -Wall + -Wextra # reasonable and standard + -Wshadow # warn the user if a variable declaration shadows one from a parent context + -Wnon-virtual-dtor # warn the user if a class with virtual functions has a non-virtual destructor. This helps catch + # hard to track down memory errors + -Wold-style-cast # warn for c-style casts + -Wcast-align # warn for potential performance problem casts + -Wunused # warn on anything being unused + -Woverloaded-virtual # warn if you overload (not override) a virtual function + -Wpedantic # warn if non-standard C++ is used + -Wconversion # warn on type conversions that may lose data + -Wsign-conversion # warn on sign conversions + -Wnull-dereference # warn if a null dereference is detected + -Wdouble-promotion # warn if float is implicit promoted to double + -Wformat=2 # warn on security issues around functions that format output (ie printf) + -Wimplicit-fallthrough # warn on statements that fallthrough without an explicit annotation +) + +if (WARNINGS_AS_ERRORS) + set(CXX_WARNINGS ${CXX_WARNINGS} -Werror) +endif () + +set(C_WARNINGS + -Wall + -Wextra # reasonable and standard + -Wshadow # warn the user if a variable declaration shadows one from a parent context + -Wcast-align # warn for potential performance problem casts + -Wunused # warn on anything being unused + -Wpedantic # warn if non-standard C is used + -Wconversion # warn on type conversions that may lose data + -Wsign-conversion # warn on sign conversions + -Wnull-dereference # warn if a null dereference is detected + -Wdouble-promotion # warn if float is implicit promoted to double + -Wformat=2 # warn on security issues around functions that format output (ie printf) + -Wimplicit-fallthrough # warn on statements that fallthrough without an explicit annotation +) + +if (WARNINGS_AS_ERRORS) + set(C_WARNINGS ${C_WARNINGS} -Werror) +endif () diff --git a/src/cmake/compiler/warnings/gcc-warnings.cmake b/src/cmake/compiler/warnings/gcc-warnings.cmake new file mode 100644 index 00000000..db10bbc1 --- /dev/null +++ b/src/cmake/compiler/warnings/gcc-warnings.cmake @@ -0,0 +1,12 @@ +# based off https://github.com/cpp-best-practices/cpp_starter_project/blob/main/cmake/CompilerWarnings.cmake + +include(clang-warnings) + +# GCC accepts the same set of warnings as clang, and then some +set(CXX_WARNINGS + ${CXX_WARNINGS} + -Wmisleading-indentation # warn if indentation implies blocks where blocks do not exist + -Wduplicated-cond # warn if if / else chain has duplicated conditions + -Wduplicated-branches # warn if if / else branches have duplicated code + -Wlogical-op # warn about logical operations being used where bitwise were probably wanted +) diff --git a/src/cmake/compiler/warnings/msvc-warnings.cmake b/src/cmake/compiler/warnings/msvc-warnings.cmake new file mode 100644 index 00000000..3b1295b8 --- /dev/null +++ b/src/cmake/compiler/warnings/msvc-warnings.cmake @@ -0,0 +1,58 @@ +# based off https://github.com/cpp-best-practices/cpp_starter_project/blob/main/cmake/CompilerWarnings.cmake + +set(CXX_WARNINGS + /W4 # Baseline reasonable warnings + /w14242 # 'identifier': conversion from 'type1' to 'type1', possible loss of data + /w14254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data + /w14263 # 'function': member function does not override any base class virtual member function + /w14265 # 'classname': class has virtual functions, but destructor is not virtual instances of this class may not be + # destructed correctly + /w14287 # 'operator': unsigned/negative constant mismatch + /we4289 # nonstandard extension used: 'variable': loop control variable declared in the for-loop is used outside the + # for-loop scope + /w14296 # 'operator': expression is always 'boolean_value' + /w14311 # 'variable': pointer truncation from 'type1' to 'type2' + /w14545 # expression before comma evaluates to a function which is missing an argument list + /w14546 # function call before comma missing argument list + /w14547 # 'operator': operator before comma has no effect; expected operator with side-effect + /w14549 # 'operator': operator before comma has no effect; did you intend 'operator'? + /w14555 # expression has no effect; expected expression with side- effect + /w14619 # pragma warning: there is no warning number 'number' + /w14640 # Enable warning on thread un-safe static member initialization + /w14826 # Conversion from 'type1' to 'type_2' is sign-extended. This may cause unexpected runtime behavior. + /w14905 # wide string literal cast to 'LPSTR' + /w14906 # string literal cast to 'LPWSTR' + /w14928 # illegal copy-initialization; more than one user-defined conversion has been implicitly applied + /permissive- # standards conformance mode for MSVC compiler. +) + +if (WARNINGS_AS_ERRORS) + set(CXX_WARNINGS ${CXX_WARNINGS} /WX) +endif () + +set(C_WARNINGS + /W4 # Baseline reasonable warnings + /w14242 # 'identifier': conversion from 'type1' to 'type1', possible loss of data + /w14254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data + /w14287 # 'operator': unsigned/negative constant mismatch + /we4289 # nonstandard extension used: 'variable': loop control variable declared in the for-loop is used outside the + # for-loop scope + /w14296 # 'operator': expression is always 'boolean_value' + /w14311 # 'variable': pointer truncation from 'type1' to 'type2' + /w14545 # expression before comma evaluates to a function which is missing an argument list + /w14546 # function call before comma missing argument list + /w14547 # 'operator': operator before comma has no effect; expected operator with side-effect + /w14549 # 'operator': operator before comma has no effect; did you intend 'operator'? + /w14555 # expression has no effect; expected expression with side- effect + /w14619 # pragma warning: there is no warning number 'number' + /w14640 # Enable warning on thread un-safe static member initialization + /w14826 # Conversion from 'type1' to 'type_2' is sign-extended. This may cause unexpected runtime behavior. + /w14905 # wide string literal cast to 'LPSTR' + /w14906 # string literal cast to 'LPWSTR' + /w14928 # illegal copy-initialization; more than one user-defined conversion has been implicitly applied + /permissive- # standards conformance mode for MSVC compiler. +) + +if (WARNINGS_AS_ERRORS) + set(C_WARNINGS ${C_WARNINGS} /WX) +endif () diff --git a/src/cmake/compiler_opts.cmake b/src/cmake/compiler_opts.cmake new file mode 100644 index 00000000..964331bc --- /dev/null +++ b/src/cmake/compiler_opts.cmake @@ -0,0 +1,91 @@ +# Ricochet Refresh - https://ricochetrefresh.net/ +# Copyright (C) 2021, Blueprint For Free Speech +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# +# * Neither the names of the copyright owners nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/compiler/warnings) +function (setup_compiler_warnings target) + option(WARNINGS_AS_ERRORS "Treat compiler warnings as errors" OFF) + if (CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + include(clang-warnings) + elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + include(gcc-warnings) + elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + include(msvc-warnings) + else () + message(WARNING "No compiler specific warning settings detected for compiler ${CMAKE_CXX_COMPILER_ID}") + endif () + + target_compile_options(${target} PRIVATE $<$:${CXX_WARNINGS}> + $<$:${C_WARNINGS}>) +endfunction () + +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/compiler/san) +function (setup_compiler_sanitizers target) + if (CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + include(clang-san) + clang_setup_sanitizers(${target}) + elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + include(gcc-san) + gcc_setup_sanitizers(${target}) + elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + include(msvc-san) + msvc_setup_sanitizers(${target}) + else () + message(WARNING "No compiler specific sanitizer settings detected for compiler ${CMAKE_CXX_COMPILER_ID}") + endif () +endfunction () + +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/compiler/static) +function (setup_compiler_static_flags target) + set(TEGO_STATIC_BUILD OFF CACHE BOOL "Enable compiler specific static build flags") + if (TEGO_STATIC_BUILD) + if (MINGW) + include(mingw-static) + mingw_setup_static_build(${target}) + elseif (CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + include(clang-static) + clang_setup_static_build(${target}) + elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + include(gcc-static) + gcc_setup_static_build(${target}) + elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + include(msvc-static) + msvc_setup_static_build(${target}) + else () + message(WARNING "No compiler specific static build settings detected for compiler ${CMAKE_CXX_COMPILER_ID}") + endif () + endif () +endfunction () + +function (setup_compiler target) + setup_compiler_warnings(${target}) + setup_compiler_sanitizers(${target}) + setup_compiler_static_flags(${target}) +endfunction () diff --git a/src/cmake/lto.cmake b/src/cmake/lto.cmake new file mode 100644 index 00000000..b2446ea3 --- /dev/null +++ b/src/cmake/lto.cmake @@ -0,0 +1,45 @@ +# Ricochet Refresh - https://ricochetrefresh.net/ +# Copyright (C) 2021, Blueprint For Free Speech +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# +# * Neither the names of the copyright owners nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +# Enables LTO if available and requested (disable with windows targets when statically linking openssl and QT, see issue +# #104) + +option(ENABLE_LTO "Enable Link Time Optimization (LTO)" ON) +if (ENABLE_LTO) + include(CheckIPOSupported) + check_ipo_supported(RESULT have_ipo OUTPUT err) + if (have_ipo) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) + else () + message(SEND_ERROR "IPO requested, but not supported: ${err}") + endif () +endif () diff --git a/src/cmake/qmake_static.cmake b/src/cmake/qmake_static.cmake new file mode 100644 index 00000000..e678d527 --- /dev/null +++ b/src/cmake/qmake_static.cmake @@ -0,0 +1,140 @@ +# Ricochet Refresh - https://ricochetrefresh.net/ +# Copyright (C) 2021, Blueprint For Free Speech +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# +# * Neither the names of the copyright owners nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# QT doesn't play very nicely with static builds and cmake +# One of the issues is that QT will not add the plugin libraries at link time, +# so it has to be done manually + +# Usage: +# Will automatically find and link plugin libraries when using a static build +# of QT. Expects STATIC_QT_ROOT_DIR to be the root path of the static build of +# the QT SDK. +# Assumes the QML files for the target are located in a subdirectory of +# CMAKE_CURRENT_SOURCE_DIR +# Example: +# target_generate_static_qml_plugins(ricochet-refresh) +function (target_generate_static_qml_plugins target) + # Find all the plugins required using qmlimportscanner + execute_process( + COMMAND "${STATIC_QT_ROOT_DIR}/bin/qmlimportscanner" -rootPath "${CMAKE_CURRENT_SOURCE_DIR}" -importPath "${STATIC_QT_ROOT_DIR}/qml" + OUTPUT_VARIABLE QML_IMPORT_JSON + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + # convert the output into a list by matching everything between {} + string(REGEX MATCHALL "\\{[^\\}]+}" QML_IMPORT_LIST ${QML_IMPORT_JSON}) + + # qmlimportscanner outputs in JSON, we want to extract all the "classname" + # and "plugin" values + set(PLUGIN_CLASSES "") + set(PLUGIN_NAMES "") + foreach (LINE ${QML_IMPORT_LIST}) + # try extract the class name + string(REGEX MATCH "\"classname\"\\: \"([a-zA-Z0-9]*)\"" PLUGIN_CLASS ${LINE}) + if (CMAKE_MATCH_1 AND NOT CMAKE_MATCH_1 STREQUAL "") + set(PLUGIN_CLASS ${CMAKE_MATCH_1}) + list(APPEND PLUGIN_CLASSES ${CMAKE_MATCH_1}) + + # try extract the plugin name + string(REGEX MATCH "\"plugin\"\\: \"([a-zA-Z0-9_]*)\"" PLUGIN_NAME ${LINE}) + if (CMAKE_MATCH_1 AND NOT CMAKE_MATCH_1 STREQUAL "") + set(PLUGIN_NAME ${CMAKE_MATCH_1}) + list(APPEND PLUGIN_NAMES ${CMAKE_MATCH_1}) + + # try extract the path hints + string(REGEX MATCH "\"path\"\\: \"([^\"]*)\"" PLUGIN_PATH ${LINE}) + if (CMAKE_MATCH_1 AND NOT CMAKE_MATCH_1 STREQUAL "") + set(${PLUGIN_NAME}_PATH ${CMAKE_MATCH_1}) + endif () + endif () + endif () + endforeach() + + list(REMOVE_DUPLICATES PLUGIN_CLASSES) + list(REMOVE_DUPLICATES PLUGIN_NAMES) + + # write the cpp file + set(STATIC_QML_PLUGIN_SRC_FILE ${CMAKE_CURRENT_BINARY_DIR}/${target}-qml_plugin_imports.cpp) + file(WRITE ${STATIC_QML_PLUGIN_SRC_FILE} + "#include \n") + foreach (CLASSNAME ${PLUGIN_CLASSES}) + file(APPEND ${STATIC_QML_PLUGIN_SRC_FILE} + "Q_IMPORT_PLUGIN(${CLASSNAME})\n") + endforeach () + target_sources(${target} PRIVATE ${STATIC_QML_PLUGIN_SRC_FILE}) + + # add the libraries to the link line + foreach (PLUGIN_NAME ${PLUGIN_NAMES}) + # TODO: this works for now, but it might break if we ever use plugins + # that qmlimportscanner doesn't provide a path for + find_library(${PLUGIN_NAME}_lib ${PLUGIN_NAME} HINTS ${${PLUGIN_NAME}_PATH}) + if (${${PLUGIN_NAME}_lib} STREQUAL "${PLUGIN_NAME}_lib-NOTFOUND") + message(FATAL_ERROR "Could not find plugin library for plugin ${PLUGIN_NAME}. Tried ${${PLUGIN_NAME}_PATH} (${${PLUGIN_NAME}_lib})") + endif () + + target_link_libraries(${target} PRIVATE ${${PLUGIN_NAME}_lib}) + endforeach () +endfunction () + +# Same usage as above, but for QT plugins +function (target_generate_static_qt_plugins target) + # Get *all* plugin group names from QT_DIR/lib/cmake + file(GLOB PLUGINGROUPS + LIST_DIRECTORIES ON + RELATIVE "${STATIC_QT_ROOT_DIR}/lib/cmake" + "${STATIC_QT_ROOT_DIR}/lib/cmake/*") + + # On each of those groups, if a plugin is needed then QT should set + # ${PLUGIN_GROUP}_DIR and ${PLUGIN_GROUP}_PLUGINS + set(PLUGINS "") + foreach (PLUGINGROUP ${PLUGINGROUPS}) + if (NOT ("${${PLUGINGROUP}_DIR}" STREQUAL "") AND NOT ("${${PLUGINGROUP}_PLUGINS}" STREQUAL "")) + # Get all the plugins for this group + foreach (PLUGIN_LIB ${${PLUGINGROUP}_PLUGINS}) + # Remove the leading "Qt::" + string(REGEX REPLACE "Qt.::(.*)$" "\\1" PLUGIN ${PLUGIN_LIB}) + + target_link_libraries(${target} PRIVATE ${PLUGIN_LIB}) + list(APPEND PLUGINS ${PLUGIN}) + endforeach () + endif () + endforeach () + + # Write out the import file + set(STATIC_QT_PLUGIN_SRC_FILE ${CMAKE_CURRENT_BINARY_DIR}/${target}-qt_plugin_imports.cpp) + file(WRITE ${STATIC_QT_PLUGIN_SRC_FILE} + "#include \n") + foreach (CLASSNAME ${PLUGINS}) + file(APPEND ${STATIC_QT_PLUGIN_SRC_FILE} + "Q_IMPORT_PLUGIN(${CLASSNAME})\n") + endforeach () + target_sources(${target} PRIVATE ${STATIC_QT_PLUGIN_SRC_FILE}) +endfunction () \ No newline at end of file diff --git a/src/cmake/toolchains/README b/src/cmake/toolchains/README new file mode 100644 index 00000000..c52284f1 --- /dev/null +++ b/src/cmake/toolchains/README @@ -0,0 +1,10 @@ +# Toolchain files + +These files provide some early configuration during cmake configuration + some +compile time flags. These toolchain files have been written to work for our +`ricochet-build` repository, for cross compiling on a linux-(x86_64|i386) host. + +If you are trying to cross compile for another platform (or one which we +provide a toolchain file for), you may have to manually specify the +`CMAKE_PREFIX_PATH` on the command line (or in a cmake GUI) in order to +successfully build. \ No newline at end of file diff --git a/src/cmake/toolchains/i386-linux-gnu.cmake b/src/cmake/toolchains/i386-linux-gnu.cmake new file mode 100644 index 00000000..b3fd8527 --- /dev/null +++ b/src/cmake/toolchains/i386-linux-gnu.cmake @@ -0,0 +1,40 @@ +# Ricochet Refresh - https://ricochetrefresh.net/ +# Copyright (C) 2021, Blueprint For Free Speech +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# +# * Neither the names of the copyright owners nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +set(CMAKE_SYSTEM_NAME Linux CACHE STRING "" FORCE) +set(CMAKE_SYSTEM_PROCESSOR i386 CACHE STRING "" FORCE) + +set(CMAKE_C_FLAGS "-m32" CACHE STRING "" FORCE) +set(CMAKE_CXX_FLAGS "-m32" CACHE STRING "" FORCE) + +set(CMAKE_EXE_LINKER_FLAGS "-m32" CACHE STRING "" FORCE) + +set(CMAKE_PREFIX_PATH "/usr/lib/i386-linux-gnu" CACHE STRING "" FORCE) diff --git a/src/cmake/toolchains/i386-w64-mingw32.cmake b/src/cmake/toolchains/i386-w64-mingw32.cmake new file mode 100644 index 00000000..533758ad --- /dev/null +++ b/src/cmake/toolchains/i386-w64-mingw32.cmake @@ -0,0 +1,40 @@ +# Ricochet Refresh - https://ricochetrefresh.net/ +# Copyright (C) 2021, Blueprint For Free Speech +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# +# * Neither the names of the copyright owners nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +set(CMAKE_SYSTEM_NAME Windows CACHE STRING "" FORCE) +set(CMAKE_SYSTEM_PROCESSOR i386 CACHE STRING "" FORCE) + +set(CMAKE_C_FLAGS "-m32" CACHE STRING "" FORCE) +set(CMAKE_CXX_FLAGS "-m32" CACHE STRING "" FORCE) + +set(CMAKE_EXE_LINKER_FLAGS "-m32" CACHE STRING "" FORCE) + +set(CMAKE_PREFIX_PATH "/var/tmp/dist/mingw-w64/i686-w64-mingw32/lib/" CACHE STRING "" FORCE) diff --git a/src/cmake/toolchains/x86_64-apple-darwin.cmake b/src/cmake/toolchains/x86_64-apple-darwin.cmake new file mode 100644 index 00000000..73439120 --- /dev/null +++ b/src/cmake/toolchains/x86_64-apple-darwin.cmake @@ -0,0 +1,40 @@ +# Ricochet Refresh - https://ricochetrefresh.net/ +# Copyright (C) 2021, Blueprint For Free Speech +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# +# * Neither the names of the copyright owners nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +set(CMAKE_SYSTEM_NAME Darwin CACHE STRING "" FORCE) +set(CMAKE_SYSTEM_PROCESSOR x86_64 CACHE STRING "" FORCE) + +set(CMAKE_C_COMPILER_TARGET "x86_64-apple-darwin") +set(CMAKE_CXX_COMPILER_TARGET "x86_64-apple-darwin") +set(CMAKE_ASM_COMPILER_TARGET "x86_64-apple-darwin") + +set(CMAKE_PREFIX_PATH "/var/tmp/dist/macosx-toolchain/clang/lib/" CACHE STRING "" FORCE) +set(CMAKE_FRAMEWORK_PATH "/var/tmp/dist/macosx-toolchain/MacOSX10.15.sdk/System/Library/Frameworks" CACHE STRING "" FORCE) diff --git a/src/cmake/toolchains/x86_64-w64-mingw32.cmake b/src/cmake/toolchains/x86_64-w64-mingw32.cmake new file mode 100644 index 00000000..a1f3fffb --- /dev/null +++ b/src/cmake/toolchains/x86_64-w64-mingw32.cmake @@ -0,0 +1,35 @@ +# Ricochet Refresh - https://ricochetrefresh.net/ +# Copyright (C) 2021, Blueprint For Free Speech +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# +# * Neither the names of the copyright owners nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +set(CMAKE_SYSTEM_NAME Windows CACHE STRING "" FORCE) +set(CMAKE_SYSTEM_PROCESSOR x86_64 CACHE STRING "" FORCE) + +set(CMAKE_PREFIX_PATH "/var/tmp/dist/mingw-w64/x86_64-w64-mingw32/lib/" CACHE STRING "" FORCE) diff --git a/src/core/ConversationModel.cpp b/src/core/ConversationModel.cpp deleted file mode 100644 index 96c6381e..00000000 --- a/src/core/ConversationModel.cpp +++ /dev/null @@ -1,345 +0,0 @@ -/* Ricochet - https://ricochet.im/ - * Copyright (C) 2014, John Brooks - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * - * * Neither the names of the copyright owners nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "ConversationModel.h" -#include "protocol/Connection.h" -#include "protocol/ChatChannel.h" -#include - -ConversationModel::ConversationModel(QObject *parent) - : QAbstractListModel(parent) - , m_contact(0) - , m_unreadCount(0) -{ -} - -void ConversationModel::setContact(ContactUser *contact) -{ - if (contact == m_contact) - return; - - beginResetModel(); - messages.clear(); - - if (m_contact) - disconnect(m_contact, 0, this, 0); - m_contact = contact; - if (m_contact) { - auto connectChannel = [this](Protocol::Channel *channel) { - if (Protocol::ChatChannel *chat = qobject_cast(channel)) { - connect(chat, &Protocol::ChatChannel::messageReceived, this, &ConversationModel::messageReceived); - connect(chat, &Protocol::ChatChannel::messageAcknowledged, this, &ConversationModel::messageAcknowledged); - - if (chat->direction() == Protocol::Channel::Outbound) { - connect(chat, &Protocol::Channel::invalidated, this, &ConversationModel::outboundChannelClosed); - sendQueuedMessages(); - } - } - }; - - auto connectConnection = [this,connectChannel]() { - if (m_contact->connection()) { - connect(m_contact->connection().data(), &Protocol::Connection::channelOpened, this, connectChannel); - foreach (auto channel, m_contact->connection()->findChannels()) - connectChannel(channel); - sendQueuedMessages(); - } - }; - - connect(m_contact, &ContactUser::connected, this, connectConnection); - connectConnection(); - connect(m_contact, &ContactUser::statusChanged, - this, &ConversationModel::onContactStatusChanged); - } - - endResetModel(); - emit contactChanged(); -} - -void ConversationModel::sendMessage(const QString &text) -{ - if (text.isEmpty()) - return; - - MessageData message(text, QDateTime::currentDateTime(), 0, Queued); - - if (m_contact->connection()) { - auto channel = m_contact->connection()->findChannel(Protocol::Channel::Outbound); - if (!channel) { - channel = new Protocol::ChatChannel(Protocol::Channel::Outbound, m_contact->connection().data()); - if (!channel->openChannel()) { - message.status = Error; - delete channel; - channel = 0; - } - } - - if (channel && channel->isOpened()) { - MessageId id = 0; - if (channel->sendChatMessage(text, QDateTime(), id)) - message.status = Sending; - else - message.status = Error; - message.identifier = id; - message.attemptCount++; - } - } - - beginInsertRows(QModelIndex(), 0, 0); - messages.prepend(message); - endInsertRows(); - prune(); -} - -void ConversationModel::sendQueuedMessages() -{ - if (!m_contact->connection()) - return; - - // Quickly scan to see if we have any queued messages - bool haveQueued = false; - foreach (const MessageData &data, messages) { - if (data.status == Queued) { - haveQueued = true; - break; - } - } - - if (!haveQueued) - return; - - auto channel = m_contact->connection()->findChannel(Protocol::Channel::Outbound); - if (!channel) { - channel = new Protocol::ChatChannel(Protocol::Channel::Outbound, m_contact->connection().data()); - if (!channel->openChannel()) { - delete channel; - return; - } - } - - // sendQueuedMessages is called at channelOpened - if (!channel->isOpened()) - return; - - // Iterate backwards, from oldest to newest messages - for (int i = messages.size() - 1; i >= 0; i--) { - if (messages[i].status == Queued) { - qDebug() << "Sending queued chat message"; - bool ok = false; - if (messages[i].identifier) - ok = channel->sendChatMessageWithId(messages[i].text, messages[i].time, messages[i].identifier); - else - ok = channel->sendChatMessage(messages[i].text, messages[i].time, messages[i].identifier); - if (ok) - messages[i].status = Sending; - else - messages[i].status = Error; - messages[i].attemptCount++; - emit dataChanged(index(i, 0), index(i, 0)); - } - } -} - -void ConversationModel::messageReceived(const QString &text, const QDateTime &time, MessageId id) -{ - // In rare cases an outgoing acknowledgement packet can be lost which - // causes the other party to resend the message. Discard the duplicate. - // We don't need to resend the old acknowledgement packet because - // it is identical to the one for the duplicate message. - for (int i = 0; i < messages.size() && i < 5; i++) { - if (messages[i].status == Delivered) { - break; - } - if (messages[i].identifier == id && messages[i].text == text) { - qDebug() << "duplicate incoming message" << id; - return; - } - } - - // To preserve conversation flow despite potentially high latency, incoming messages - // are positioned above the last unacknowledged messages to the peer. We assume that - // the peer hadn't seen any unacknowledged message when this message was sent. - int row = 0; - for (int i = 0; i < messages.size() && i < 5; i++) { - if (messages[i].status != Sending && messages[i].status != Queued) { - row = i; - break; - } - } - - beginInsertRows(QModelIndex(), row, row); - MessageData message(text, time, id, Received); - messages.insert(row, message); - endInsertRows(); - prune(); - - m_unreadCount++; - emit unreadCountChanged(); -} - -void ConversationModel::messageAcknowledged(MessageId id, bool accepted) -{ - int row = indexOfIdentifier(id, true); - if (row < 0) - return; - - MessageData &data = messages[row]; - data.status = accepted ? Delivered : Error; - emit dataChanged(index(row, 0), index(row, 0)); -} - -void ConversationModel::outboundChannelClosed() -{ - // Any messages that are Sending are moved back to Queued, so they - // will be re-sent when we reconnect. - for (int i = 0; i < messages.size(); i++) { - if (messages[i].status != Sending) - continue; - if (messages[i].attemptCount >= 2) { - qDebug() << "Outbound chat channel closed, and unacknowledged message has been tried twice already. Marking as error."; - messages[i].status = Error; - } else { - qDebug() << "Outbound chat channel closed, putting unacknowledged chat message back in queue"; - messages[i].status = Queued; - } - emit dataChanged(index(i, 0), index(i, 0)); - } - - // Try to reopen the channel if we're still connected - if (m_contact && m_contact->connection() && m_contact->connection()->isConnected()) { - metaObject()->invokeMethod(this, "sendQueuedMessages", Qt::QueuedConnection); - } -} - -void ConversationModel::clear() -{ - if (messages.isEmpty()) - return; - - beginRemoveRows(QModelIndex(), 0, messages.size()-1); - messages.clear(); - endRemoveRows(); - - resetUnreadCount(); -} - -void ConversationModel::resetUnreadCount() -{ - if (m_unreadCount == 0) - return; - m_unreadCount = 0; - emit unreadCountChanged(); -} - -void ConversationModel::onContactStatusChanged() -{ - // Update in case section has changed - emit dataChanged(index(0, 0), index(rowCount()-1, 0), QVector() << SectionRole); -} - -QHash ConversationModel::roleNames() const -{ - QHash roles; - roles[Qt::DisplayRole] = "text"; - roles[TimestampRole] = "timestamp"; - roles[IsOutgoingRole] = "isOutgoing"; - roles[StatusRole] = "status"; - roles[SectionRole] = "section"; - roles[TimespanRole] = "timespan"; - return roles; -} - -int ConversationModel::rowCount(const QModelIndex &parent) const -{ - if (parent.isValid()) - return 0; - return messages.size(); -} - -QVariant ConversationModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid() || index.row() >= messages.size()) - return QVariant(); - - const MessageData &message = messages[index.row()]; - - switch (role) { - case Qt::DisplayRole: return message.text; - case TimestampRole: return message.time; - case IsOutgoingRole: return message.status != Received; - case StatusRole: return message.status; - - case SectionRole: { - if (m_contact->status() == ContactUser::Online) - return QString(); - if (index.row() < messages.size() - 1) { - const MessageData &next = messages[index.row()+1]; - if (next.status != Received && next.status != Delivered) - return QString(); - } - for (int i = 0; i <= index.row(); i++) { - if (messages[i].status == Received || messages[i].status == Delivered) - return QString(); - } - return QStringLiteral("offline"); - } - case TimespanRole: { - if (index.row() < messages.size() - 1) - return messages[index.row() + 1].time.secsTo(messages[index.row()].time); - else - return -1; - } - } - - return QVariant(); -} - -int ConversationModel::indexOfIdentifier(MessageId identifier, bool isOutgoing) const -{ - for (int i = 0; i < messages.size(); i++) { - if (messages[i].identifier == identifier && (messages[i].status != Received) == isOutgoing) - return i; - } - return -1; -} - -void ConversationModel::prune() -{ - const int history_limit = 1000; - if (messages.size() > history_limit) { - beginRemoveRows(QModelIndex(), history_limit, messages.size()-1); - while (messages.size() > history_limit) { - messages.removeLast(); - } - endRemoveRows(); - } -} diff --git a/src/extern/fmt b/src/extern/fmt new file mode 160000 index 00000000..0b6e7cc6 --- /dev/null +++ b/src/extern/fmt @@ -0,0 +1 @@ +Subproject commit 0b6e7cc60abd939685ebe339de1e19f008a9211f diff --git a/src/extern/tor b/src/extern/tor new file mode 160000 index 00000000..ae1466e1 --- /dev/null +++ b/src/extern/tor @@ -0,0 +1 @@ +Subproject commit ae1466e103a5b8bcbf3f561abf0379e535f54cf0 diff --git a/src/libtego/CMakeLists.txt b/src/libtego/CMakeLists.txt new file mode 100644 index 00000000..00f70a4c --- /dev/null +++ b/src/libtego/CMakeLists.txt @@ -0,0 +1,274 @@ +# Ricochet Refresh - https://ricochetrefresh.net/ +# Copyright (C) 2021, Blueprint For Free Speech +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# +# * Neither the names of the copyright owners nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +cmake_minimum_required(VERSION 3.16) + +project(libtego LANGUAGES CXX) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if (FORCE_QT5) + find_package( + QT + NAMES + Qt5 + COMPONENTS Core + Gui + Network + Quick + Widgets + REQUIRED) +else () + find_package( + QT + NAMES + Qt6 + Qt5 + COMPONENTS Core + Gui + Network + Quick + Widgets + REQUIRED) +endif () + +find_package( + Qt${QT_VERSION_MAJOR} + COMPONENTS Core + Widgets + Network + Qml + Quick + REQUIRED) + +# Require Qt >5.15 +if (${QT_VERSION_MAJOR} EQUAL 5) + if (${QT_VERSION_MINOR} LESS 15) + message(FATAL_ERROR "Qt >5.15 is required to build this project!") + endif () +endif () + +if (APPLE) + find_package( + Qt${QT_VERSION_MAJOR} + COMPONENTS MacExtras + REQUIRED) +endif () + +# protobuf configuration +set(Protobuf_USE_STATIC_LIBS OFF CACHE BOOL "Use statically-linked protobuf") +set(Protobuf_ROOT_DIR "" CACHE STRING "Path to custom Protobuf installation") +if (NOT ${Protobuf_ROOT_DIR} STREQUAL "") + # CMake doesn't allow us to specify a protobuf installation like with + # openssl. Instead, use CMAKE_PREFIX_PATH to tell CMake to check that dir + # first for a protobuf install + list(APPEND CMAKE_PREFIX_PATH ${Protobuf_ROOT_DIR}) +endif () + +include(FindProtobuf) +include(FindOpenSSL) + +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) + +add_library( + tego STATIC + include/tego/logger.hpp + include/tego/tego.h + include/tego/tego.hpp + include/tego/utilities.hpp + source/context.cpp + source/context.hpp + source/core/ContactIDValidator.cpp + source/core/ContactIDValidator.h + source/core/ContactUser.cpp + source/core/ContactUser.h + source/core/ContactsManager.cpp + source/core/ContactsManager.h + source/core/ConversationModel.cpp + source/core/ConversationModel.h + source/core/IdentityManager.cpp + source/core/IdentityManager.h + source/core/IncomingRequestManager.cpp + source/core/IncomingRequestManager.h + source/core/OutgoingContactRequest.cpp + source/core/OutgoingContactRequest.h + source/core/UserIdentity.cpp + source/core/UserIdentity.h + source/delete.cpp + source/ed25519.cpp + source/ed25519.hpp + source/error.cpp + source/error.hpp + source/file_hash.cpp + source/file_hash.hpp + source/globals.cpp + source/globals.hpp + source/libtego.cpp + source/logger.cpp + source/orconfig.h + source/precomp.h + source/protocol/AuthHiddenServiceChannel.cpp + source/protocol/AuthHiddenServiceChannel.h + source/protocol/Channel.cpp + source/protocol/Channel.h + source/protocol/Channel_p.h + source/protocol/ChatChannel.cpp + source/protocol/ChatChannel.h + source/protocol/Connection.cpp + source/protocol/Connection.h + source/protocol/Connection_p.h + source/protocol/ContactRequestChannel.cpp + source/protocol/ContactRequestChannel.h + source/protocol/ControlChannel.cpp + source/protocol/ControlChannel.h + source/protocol/FileChannel.cpp + source/protocol/FileChannel.h + source/protocol/OutboundConnector.cpp + source/protocol/OutboundConnector.h + source/signals.cpp + source/signals.hpp + source/tor.cpp + source/tor.hpp + source/tor/AddOnionCommand.cpp + source/tor/AddOnionCommand.h + source/tor/AuthenticateCommand.cpp + source/tor/AuthenticateCommand.h + source/tor/GetConfCommand.cpp + source/tor/GetConfCommand.h + source/tor/HiddenService.cpp + source/tor/HiddenService.h + source/tor/ProtocolInfoCommand.cpp + source/tor/ProtocolInfoCommand.h + source/tor/SetConfCommand.cpp + source/tor/SetConfCommand.h + source/tor/TorControl.cpp + source/tor/TorControl.h + source/tor/TorControlCommand.cpp + source/tor/TorControlCommand.h + source/tor/TorControlSocket.cpp + source/tor/TorControlSocket.h + source/tor/TorManager.cpp + source/tor/TorManager.h + source/tor/TorProcess.cpp + source/tor/TorProcess.h + source/tor/TorProcess_p.h + source/tor/TorSocket.cpp + source/tor/TorSocket.h + source/tor_stubs.cpp + source/user.cpp + source/user.hpp + source/utils/CryptoKey.cpp + source/utils/CryptoKey.h + source/utils/PendingOperation.cpp + source/utils/PendingOperation.h + source/utils/SecureRNG.cpp + source/utils/SecureRNG.h + source/utils/StringUtil.cpp + source/utils/StringUtil.h + source/utils/Useful.h) +target_precompile_headers(tego PRIVATE source/precomp.h) + +include(lto) +include(compiler_opts) +# enables compiler specific warnings/sanitizers if requested +setup_compiler(tego) + +target_compile_features(tego PRIVATE cxx_std_20) + +target_include_directories(tego PRIVATE source/) +target_include_directories(tego PUBLIC include/) + +set(EXTERN_DIR "../extern/") + +# protobuf +target_include_directories(tego PRIVATE ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/protocol) +target_link_libraries(tego PRIVATE ${Protobuf_LIBRARIES}) +protobuf_generate_cpp( + PROTO_SRC + PROTO_HDR + source/protocol/FileChannel.proto + source/protocol/ContactRequestChannel.proto + source/protocol/ControlChannel.proto + source/protocol/ChatChannel.proto + source/protocol/AuthHiddenService.proto) +target_sources(tego PRIVATE ${PROTO_SRC} ${PROTO_HDR}) + +# tor +target_include_directories(tego PRIVATE ${EXTERN_DIR}/tor ${EXTERN_DIR}/tor/src ${EXTERN_DIR}/tor/src/ext) + +list( + APPEND + TOR_SRC + ${EXTERN_DIR}/tor/src/ext/ed25519/donna/ed25519_tor.c + ${EXTERN_DIR}/tor/src/lib/encoding/binascii.c + ${EXTERN_DIR}/tor/src/lib/crypt_ops/crypto_digest_openssl.c) +target_sources(tego PRIVATE ${TOR_SRC}) + +# silence warnings on the tor sources +if (CMAKE_CXX_COMPILER_ID MATCHES ".*Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set_source_files_properties(${TOR_SRC} PROPERTIES COMPILE_FLAGS "-w") +elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + set_source_files_properties(${TOR_SRC} PROPERTIES COMPILE_FLAGS "/w") +endif () + +if (NOT USE_SUBMODULE_FMT) + find_package(fmt REQUIRED) +endif () +target_link_libraries(tego PRIVATE fmt::fmt-header-only) +target_link_libraries(tego PRIVATE OpenSSL::Crypto) +target_link_libraries(tego PRIVATE protobuf::libprotobuf) + +# QT +target_link_libraries( + tego + PRIVATE Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Widgets + Qt${QT_VERSION_MAJOR}::Network + Qt${QT_VERSION_MAJOR}::Qml + Qt${QT_VERSION_MAJOR}::Quick) + +if (APPLE) + target_link_libraries(tego PRIVATE Qt${QT_VERSION_MAJOR}::MacExtras) +endif () + +target_link_libraries(tego PRIVATE Threads::Threads) + +if ("${CMAKE_BUILD_TYPE}" MATCHES "Rel.*" OR "${CMAKE_BUILD_TYPE}" STREQUAL "MinSizeRel") + target_compile_definitions(tego PRIVATE QT_NO_DEBUG_OUTPUT QT_NO_WARNING_OUTPUT) +endif () + +add_subdirectory(test) diff --git a/src/libtego/include/tego/logger.hpp b/src/libtego/include/tego/logger.hpp new file mode 100644 index 00000000..4a28ecf7 --- /dev/null +++ b/src/libtego/include/tego/logger.hpp @@ -0,0 +1,71 @@ +#pragma once + +#ifdef ENABLE_TEGO_LOGGER + +// std +#include +#include +#include +#include +#include +#include +#include +using std::experimental::source_location; +#include + +// fmt +#include +#include + +// wrapper around fmt::print that writes to singleton log file libtego.log +class logger +{ +public: + template + static void println(const char (&format)[N], ARGS&&... args) + { + std::lock_guard guard(get_mutex()); + + auto& fs = get_stream(); + + fmt::print(fs, "[{:f}][{}] ", get_timestamp(), std::this_thread::get_id()); + fmt::print(fs, format, std::forward(args)...); + fs << std::endl; + } + + template + static void println(const char (&msg)[N]) + { + std::lock_guard guard(get_mutex()); + + auto& fs = get_stream(); + + fmt::print(fs, "[{:f}][{}] ", get_timestamp(), std::this_thread::get_id()); + fs << msg << std::endl; + } + + static void trace(const source_location& loc = source_location::current()); +private: + static std::ofstream& get_stream(); + static std::mutex& get_mutex(); + static double get_timestamp(); +}; + +#else // ENABLE_TEGO_LOGGER + +// mock no-op logger +class logger +{ +public: + template + static void println(const char (&)[N], ARGS&&...) {} + template + static void println(const char (&)[N]) {} + static void trace() {} +}; +#endif // ENABLE_TEGO_LOGGER + +// always provide these overloads +std::ostream& operator<<(std::ostream& out, const class QString& str); +std::ostream& operator<<(std::ostream& out, const class QByteArray& blob); + diff --git a/src/libtego/include/tego/tego.h b/src/libtego/include/tego/tego.h new file mode 100644 index 00000000..ce2ab5f5 --- /dev/null +++ b/src/libtego/include/tego/tego.h @@ -0,0 +1,1439 @@ +#ifndef TEGO_H +#define TEGO_H + +// TODO: switch to east-const + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +#define TEGO_TRUE 1 +#define TEGO_FALSE 0 +typedef int32_t tego_bool_t; + +#define TEGO_FLAG(N) (1 << N) + +// number of bytes in an ed25519 signature +#define TEGO_ED25519_SIGNATURE_SIZE 64 +// length of a valid v3 service id string not including null terminator +#define TEGO_V3_ONION_SERVICE_ID_LENGTH 56 +// length of a v3 service id string including null terminator +#define TEGO_V3_ONION_SERVICE_ID_SIZE (TEGO_V3_ONION_SERVICE_ID_LENGTH + 1) +// length of the ed25519 KeyBlob string not including null terminator +#define TEGO_ED25519_KEYBLOB_LENGTH 99 +// length of an ed25519 keyblob string including null terminator +#define TEGO_ED25519_KEYBLOB_SIZE (TEGO_ED25519_KEYBLOB_LENGTH + 1) + +typedef struct tego_error tego_error_t; + +/* + * Get error message form tego_error + * + * @param error : the error object to get the message from + * @return : null terminated string with error message whose + * lifetime is tied to the source tego_error_t + */ +const char* tego_error_get_message(const tego_error_t* error); + +// library init/uninit + +typedef struct tego_context tego_context_t; + +void tego_initialize( + tego_context_t** out_context, + tego_error_t** error); + +void tego_uninitialize( + tego_context_t* context, + tego_error_t** error); + +/* + * v3 onion/ed25519 functionality + */ + +typedef struct tego_ed25519_private_key tego_ed25519_private_key_t; +typedef struct tego_ed25519_public_key tego_ed25519_public_key_t; +typedef struct tego_ed25519_signature tego_ed25519_signature_t; +typedef struct tego_v3_onion_service_id tego_v3_onion_service_id_t; + +/* + * Conversion method for converting the KeyBlob string returned by + * ADD_ONION command into an ed25519_private_key_t + * + * @param out_privateKey : returned ed25519 private key + * @param keyBlob : an ED25519 KeyBlob string in the form + * "ED25519-V3:abcd1234..." + * @param keyBlobLength : number of characters in keyBlob not + * counting the null terminator + * @param error : filled on error + */ +void tego_ed25519_private_key_from_ed25519_keyblob( + tego_ed25519_private_key_t** out_privateKey, + const char* keyBlob, + size_t keyBlobLength, + tego_error_t** error); + +/* + * Conversion method for converting an ed25519 private key + * to a null-terminated KeyBlob string for use with ADD_ONION + * command + * + * @param out_keyBlob : buffer to be filled with ed25519 KeyBlob in + * the form "ED25519-V3:abcd1234...\0" + * @param keyBlobSize : size of out_keyBlob buffer in bytes, must be at + * least 100 characters (99 for string + 1 for null terminator) + * @param privateKey : the private key to encode + * @param error : filled on error + * @return : the number of characters written (including null terminator) + * to out_keyBlob + */ +size_t tego_ed25519_keyblob_from_ed25519_private_key( + char *out_keyBlob, + size_t keyBlobSize, + const tego_ed25519_private_key_t* privateKey, + tego_error_t** error); + +/* + * Calculate ed25519 public key from ed25519 private key + * + * @param out_publicKey : returned ed25519 public key + * @param privateKey : input ed25519 private key + * @param error : filled on error + */ +void tego_ed25519_public_key_from_ed25519_private_key( + tego_ed25519_public_key_t** out_publicKey, + const tego_ed25519_private_key_t* privateKey, + tego_error_t** error); + +/* + * Checks if a service id string is valid per tor rend spec: + * https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt + * + * @param serviceIdString : string containing the v3 service id to be validated + * @param serviceIdStringLength : length of serviceIdString not counting the + * null terminator + * @param error : filled on error + */ +tego_bool_t tego_v3_onion_service_id_string_is_valid( + const char* serviceIdString, + size_t serviceIdStringLength, + tego_error_t** error); + +/* + * Construct a service id object from string. Validates + * the checksum and version byte per spec: + * https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt + * + * @param out_serviceId : returned v3 onion service id + * @param serviceIdString : a string beginning with a v3 service id + * @param serviceIdStringLength : length of the service id string not + * counting the null terminator + * @param error : filled on error + */ +void tego_v3_onion_service_id_from_string( + tego_v3_onion_service_id_t** out_serviceId, + const char* serviceIdString, + size_t serviceIdStringLength, + tego_error_t** error); + +/* + * Serializes out a service id object as a null-terminated utf8 string + * to provided character buffer. + * + * @param serviceId : v3 onion service id object to serialize + * @param out_serviceIdString : destination buffer for string + * @param serviceIdStringSize : size of out_serviceIdString buffer in + * bytes, must be at least 57 bytes (56 bytes for string + null + * terminator) + * @param error : filled on error + * @return : number of bytes written including null terminator; + * TEGO_V3_ONION_SERVICE_ID_SIZE (57) on success, 0 on failure + */ +size_t tego_v3_onion_service_id_to_string( + const tego_v3_onion_service_id_t* serviceId, + char* out_serviceIdString, + size_t serviceIdStringSize, + tego_error_t** error); + +/* + * Extract public key from v3 service id per + * https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt + * + * @param out_publicKey : returned ed25519 public key + * @param serviceId : input service id + * @param error : filled on error + */ +void tego_ed25519_public_key_from_v3_onion_service_id( + tego_ed25519_public_key_t** out_publicKey, + const tego_v3_onion_service_id_t* serviceId, + tego_error_t** error); + +/* + * Derive an onion's service id from its ed25519 public key per + * https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt + * + * @param out_serviceId : returned service id + * @param publicKey : the public key input + * @param error : filled on error + */ +void tego_v3_onion_service_id_from_ed25519_public_key( + tego_v3_onion_service_id_t** out_serviceId, + const tego_ed25519_public_key_t* publicKey, + tego_error_t** error); + +/* + * Read in signature from length 64 byte buffer + * + * @param out_signature : returned ed25519 signature + * @param buffer : source memory buffer holding signature + * @param bufferSize : size of data in bytes, must be at least 64 bytes + * @param error : filled on error + */ +void tego_ed25519_signature_from_bytes( + tego_ed25519_signature_t** out_signature, + const uint8_t* buffer, + size_t bufferSize, + tego_error_t** error); + +/* + * Get the signature and place it in length 64 byte buffer + * + * @param signature : a calculated message signature + * @param out_buffer : output buffer to write signature to + * @param bufferSize : size of buffer in bytes, must be at least 64 bytes + * @param error : filled on error + * @return : number of bytes written to out_buffer + */ +size_t tego_ed25519_signature_to_bytes( + const tego_ed25519_signature_t* signature, + uint8_t* out_buffer, + size_t bufferSize, + tego_error_t** error); + +/* + * Sign a message with an ed25519 key-pair + * + * @param message : message binary to sign + * @param messageSize : size of message in bytes + * @param privateKey : the ed25519 private key + * @param publicKey : the ed25519 public key + * @param out_signature : the output signature + * @param error : filled on error + */ +void tego_message_ed25519_sign( + const uint8_t* message, + size_t messageSize, + const tego_ed25519_private_key_t* privateKey, + const tego_ed25519_public_key_t* publicKey, + tego_ed25519_signature_t** out_signature, + tego_error_t** error); + +/* + * Verify a message's signature given a public key + * + * @param signature : the signature to verify + * @param message : the message that was signed + * @param messageSize : size of the message in bytes + * @param publicKey : the public key to very the the signature against + * @param error : filled on error + * @return : TEGO_TRUE if signature is verified, TEGO_FALSE if it is not + * verified or if an error occurs + */ +int tego_ed25519_signature_verify( + const tego_ed25519_signature_t* signature, + const uint8_t* message, + size_t messageLength, + const tego_ed25519_public_key_t* publicKey, + tego_error_t** error); + +/* + * Chat protocol functionality + */ + +/* + * a unique identifier for a user, currently a v3 onion service id internally + */ +typedef struct tego_user_id tego_user_id_t; + +// user id + + +/* + * Duplicate a user id object + * + * @param userId : user id to copy + * @param out_userId : returned user id + * @param error : filled on error + */ +void tego_user_id_copy( + tego_user_id_t const* userId, + tego_user_id_t** out_userId, + tego_error_t** error); + +/* + * Convert a v3 onion service id to a user id + * + * @param out_userId : returned user id + * @param serviceId : input v3 onion service id + * @param error : filled on error + */ +void tego_user_id_from_v3_onion_service_id( + tego_user_id_t** out_userId, + const tego_v3_onion_service_id_t* serviceId, + tego_error_t** error); + +/* + * Get the v3 onion service id from the user id + * + * @param userId : input user id + * @param out_serviceId : returned v3 onion service id + * @param error : filled on error + */ +void tego_user_id_get_v3_onion_service_id( + const tego_user_id_t* userId, + tego_v3_onion_service_id_t** out_serviceId, + tego_error_t** error); + +// +// contacts/user methods +// + +/* + * Get the host's user_id (derived from private key) + * + * @param context : the current tego context + * @param out_hostUser : returned user id + * @param error : filled on error + */ +void tego_context_get_host_user_id( + const tego_context_t* context, + tego_user_id_t** out_hostUser, + tego_error_t** error); + +// state of the host user, encapsulates all of the tor daemon launch, +// network connection, and onion service creation into 'connecting' +// TODO: squish these into the tego_user_status_t ? +typedef enum +{ + tego_host_user_state_unknown, + tego_host_user_state_offline, + tego_host_user_state_connecting, + tego_host_user_state_online, +} tego_host_user_state_t; + +/* + * Get the current state of the host user + * + * @param context : the current tego context + * @param out_state : destination to save state + * @param error : filled on error + */ +void tego_context_get_host_user_state( + const tego_context_t* context, + tego_host_user_state_t* out_state, + tego_error_t** error); + +// TODO: figure out which statuses we need later +typedef enum +{ + tego_user_status_none, + tego_user_status_online, + tego_user_status_offline, +} tego_user_status_t; + +/* + * Get a user's current user status + * + * @param context : the current tego context + * @param user : the user whose status we want + * @param out_status : returned user status + * @param error : filled on error + */ +void tego_context_get_user_status( + const tego_context_t* context, + const tego_user_id_t* user, + tego_user_status_t* out_status, + tego_error_t** error); + +// enum for user type +typedef enum +{ + tego_user_type_host, // the host user + tego_user_type_allowed, // in host's contact list + tego_user_type_requesting, // users who have added host but the host has not replied yet + tego_user_type_blocked, // users who have added host but the host has rejected + tego_user_type_pending, // users the host has added but who have not replied yet + tego_user_type_rejected, // user the host has added but replied with rejection +} tego_user_type_t; + +/* + * Get the type of a given user + * + * @param context : the current tego context + * @param user : the given user + * @param out_type : filled with type on success + * @param error : filled on error + */ +void tego_context_get_user_type( + const tego_context_t* context, + const tego_user_id_t* user, + tego_user_type_t* out_type, + tego_error_t** error); + +/* + * Get the number of users managed by our tego context + * + * @param context : the current tego context + * @param out_userCount : gets the number of users + * @param error : filled on error + */ +void tego_context_get_user_count( + const tego_context_t* context, + size_t* out_userCount, + tego_error_t** error); + +/* + * Get all of our users of a given type + * + * @param context : the current tego context + * @param out_usersBuffer : destination buffer to store returned user id pointers + * @param usersBufferLength : maximum nuber of users that can be written to + * out_usersBuffer + * @param out_usersCount : destination to store number of user ids written + * @param error : filled on error + */ +void tego_context_get_users( + const tego_context_t* context, + tego_user_id_t** out_usersBuffer, + size_t usersBufferLength, + size_t* out_userCount, + tego_error_t** error); + +// +// Tor Config +// + +// struct that will contain configuration information for running the tor daemon +typedef struct tego_tor_launch_config tego_tor_launch_config_t; + +/* + * Init a default tor configuration struct + * + * @param out_launchConfig : destination to write pointer to empty tor configuration + * @apram error : filled on error + */ +void tego_tor_launch_config_initialize( + tego_tor_launch_config_t** out_launchConfig, + tego_error_t** error); + +/* + * Set the root directory for the tor daemon to save/read settings + * + * @param torConfig : config struct to save to + * @param dataDirectory : our desired data directory + * @param dataDirectoryLength : length of dataDirectory string not counting the + * null termiantor + * @param error : filled on error + */ +void tego_tor_launch_config_set_data_directory( + tego_tor_launch_config_t* launchConfig, + const char* dataDirectory, + size_t dataDirectoryLength, + tego_error_t** error); + +/* + * Start an instance of the tor daemon and associate it with the given context + * + * @param context : the current tego context + * @param torConfig : tor configuration params + * @param error : filled on error + */ +void tego_context_start_tor( + tego_context_t* context, + const tego_tor_launch_config* torConfig, + tego_error_t** error); + +typedef struct tego_tor_daemon_config tego_tor_daemon_config_t; + +/* + * Determine whether the tor daemon has an existing torrc and + * is already configured + * + * @param context : the current tego context + * @param out_configured : destination for result, TEGO_TRUE if has config, else TEGO_FALSE + * @param error : filled on error + */ +void tego_context_get_tor_daemon_configured( + const tego_context_t* context, + tego_bool_t* out_configured, + tego_error_t** error); + +/* + * Returns a tor daemon config struct with default params + * + * @param out_config : destination for config + * @param error : filled on error + */ +void tego_tor_daemon_config_initialize( + tego_tor_daemon_config_t** out_config, + tego_error_t** error); + +/* + * Set the DisableNetwork flag (see Tor Manual : + * www.torproject.org/docs/tor-manual.html ) + * + * @param config : config to update + * @param disableNetwork : TEGO_TRUE or TEGO_FALSE + * @param error : filled on error + */ +void tego_tor_daemon_config_set_disable_network( + tego_tor_daemon_config_t* config, + tego_bool_t disableNetwork, + tego_error_t** error); + +/* + * Set up SOCKS4 proxy params, overwrites any existing + * proxy settings + * + * @param config : config to update + * @param address : proxy addess as encoded utf8 string + * @param addressLength : length of the address not counting + * the null terminator + * @param port : proxy port, 0 not allowed + * @param error : filled on error + */ +void tego_tor_daemon_config_set_proxy_socks4( + tego_tor_daemon_config_t* config, + const char* address, + size_t addressLength, + uint16_t port, + tego_error_t** error); + +/* + * Set up SOCKS5 proxy params, overwrites any existing + * proxy settings + * + * @param config : config to update + * @param address : proxy addess encoded as utf8 string + * @param addressLength : length of the address not counting + * any NULL terminator + * @param port : proxy port, 0 not allowed + * @param username : authentication username encoded as utf8 + * string, may be NULL or empty string if not needed + * @param usernameLength : length of username string not counting + * any NULL terminator + * @param password : authentication password encoded as utf8 + * string, may be NULL or empty string if not needed + * @param passwordLength : lenght of the password string not + * counting any NULL terminator + * @param error : filled on error + */ +void tego_tor_daemon_config_set_proxy_socks5( + tego_tor_daemon_config_t* config, + const char* address, + size_t addressLength, + uint16_t port, + const char* username, + size_t usernameLength, + const char* password, + size_t passwordLength, + tego_error_t** error); + +/* + * Set up HTTPS proxy params, overwrites any existing + * proxy settings + * + * @param config : config to update + * @param address : proxy addess encoded as utf8 string + * @param addressLength : length of the address not counting + * any NULL terminator + * @param port : proxy port, 0 not allowed + * @param username : authentication username encoded as utf8 + * string, may be NULL or empty string if not needed + * @param usernameLength : length of username string not counting + * any NULL terminator + * @param password : authentication password encoded as utf8 + * string, may be NULL or empty string if not needed + * @param passwordLength : lenght of the password string not + * counting any NULL terminator + * @param error : filled on error + */ +void tego_tor_daemon_config_set_proxy_https( + tego_tor_daemon_config_t* config, + const char* address, + size_t addressLength, + uint16_t port, + const char* username, + size_t usernameLength, + const char* password, + size_t passwordLength, + tego_error_t** error); + +/* + * Set the allowed ports the tor daemon may use + * + * @param config : config to update + * @param ports : array of allowed ports + * @param portsCount : the number of ports in list + * @param error : filled on error + */ +void tego_tor_daemon_config_set_allowed_ports( + tego_tor_daemon_config_t* config, + const uint16_t* ports, + size_t portsCount, + tego_error_t** error); + +/* + * Set the list of bridges for tor to use + * + * @param config : config to update + * @param bridges : array of utf8 encoded bridge strings + * @param bridgeLengths : array of lengths of the strings stored + * in 'bridges', does not include any NULL terminators + * @param bridgeCount : the number of bridge strings being + * passed in + * @param error : filled on error + */ +void tego_tor_daemon_config_set_bridges( + tego_tor_daemon_config_t* config, + const char** bridges, + size_t* bridgeLengths, + size_t bridgeCount, + tego_error_t** error); + +/* + * Update the tor daemon settings of running instance of tor associated + * with a given tego context + * + * @param context : the current tego context + * @param torConfig : tor configuration params + * @param error : filled on error + */ +void tego_context_update_tor_daemon_config( + tego_context_t* context, + const tego_tor_daemon_config_t* torConfig, + tego_error_t** error); + +/* + * Save the courrent tor configuration to disk + * + * @param context : the current tego context + * @param error : filled on error + */ +void tego_context_save_tor_daemon_config( + tego_context_t* context, + tego_error_t** error); +/* + * Stops tor daemon associated with a given tego context + * + * @param context : the current tego context + * @param error: filled on error + */ +void tego_context_stop_tor( + tego_context_t* context, + tego_error_t** error); + +/* + * Start tego's onion service and try to connect to users + * + * @param context : the current tego context + * @param hostPrivateKey : the hosts private ed25519 key, or null if + * we want to create a new identity + * @param userBuffer : the list of all users we care about + * @param userTypeBuffer : the types associated with all of our users + * @param userCount : the length of the user and user type buffers + * @param error : filled on error + */ +void tego_context_start_service( + tego_context_t* context, + tego_ed25519_private_key_t const* hostPrivateKey, + tego_user_id_t const* const* userBuffer, + tego_user_type_t* const userTypeBuffer, + size_t userCount, + tego_error_t** error); + +/* + * Stop tego's onion service associated with the given context + * + * @param context : the current tego context + * @param error : filled on error */ +void tego_context_stop_service( + tego_context_t* context, + tego_error_t** error); + +/* + * Returns the number of charactres required (including null) to + * write out the tor logs + * + * @param context : the current tego context + * @param error : filled on error + * @return : the number of characters required + */ +size_t tego_context_get_tor_logs_size( + const tego_context_t* context, + tego_error_t** error); + +/* + * Fill the passed in buffer with the tor daemon's logs, each entry delimitted + * by newline character '\n' + * + * @param context : the current tego context + * @param out_logBuffer : user allocated buffer where tor log is to be written + * @param logBufferSize : the size of the passed in out_logBuffer buffer + * @param error : filled on error + * @return : the nuber of characters written (including null terminator) to + * out_logBuffer + */ +size_t tego_context_get_tor_logs( + const tego_context_t* context, + char* out_logBuffer, + size_t logBufferSize, + tego_error_t** error); + +/* + * Get the null-terminated tor version string + * + * @param context : the curent tego context + * @param error : filled on error + * @return : the version string for the context's running tor daemon + */ +const char* tego_context_get_tor_version_string( + const tego_context_t* context, + tego_error_t** error); + +// corresponds to Ricochet's Tor::TorControl::Status enum +typedef enum +{ + tego_tor_control_status_error = -1, + tego_tor_control_status_not_connected, + tego_tor_control_status_connecting, + tego_tor_control_status_authenticating, + tego_tor_control_status_connected, +} tego_tor_control_status_t; + +/* + * Get the current status of our tor control channel + * + * @param context : the current tego context + * @param out_status : destination to save control status + * @param error : filled on error + */ +void tego_context_get_tor_control_status( + const tego_context_t* context, + tego_tor_control_status_t* out_status, + tego_error_t** error); + +typedef enum +{ + tego_tor_process_status_unknown, + tego_tor_process_status_external, + tego_tor_process_status_not_started, + tego_tor_process_status_starting, + tego_tor_process_status_running, + tego_tor_process_status_failed, +} tego_tor_process_status_t; + +/* + * Get the current status of the tor daemon process + * + * @param context : the current tego context + * @param out_status : destination to write process status + * @param error : filled on error + */ +void tego_context_get_tor_process_status( + const tego_context_t* context, + tego_tor_process_status_t* out_status, + tego_error_t** error); + +typedef enum +{ + tego_tor_network_status_unknown, + tego_tor_network_status_ready, + tego_tor_network_status_offline, +} tego_tor_network_status_t; + +/* + * Get the current status of the tor daemon's connection + * to the tor network + * + * @param context : the current tego context + * @param out_status : destination to save network status + * @param error : filled on error + */ +void tego_context_get_tor_network_status( + const tego_context_t* context, + tego_tor_network_status_t* out_status, + tego_error_t** error); + +// see https://gitweb.torproject.org/torspec.git/tree/control-spec.txt#n3867 +typedef enum +{ + tego_tor_bootstrap_tag_invalid = -1, + tego_tor_bootstrap_tag_starting, + tego_tor_bootstrap_tag_conn_pt, + tego_tor_bootstrap_tag_conn_done_pt, + tego_tor_bootstrap_tag_conn_proxy, + tego_tor_bootstrap_tag_conn_done_proxy, + tego_tor_bootstrap_tag_conn, + tego_tor_bootstrap_tag_conn_done, + tego_tor_bootstrap_tag_handshake, + tego_tor_bootstrap_tag_handshake_done, + tego_tor_bootstrap_tag_onehop_create, + tego_tor_bootstrap_tag_requesting_status, + tego_tor_bootstrap_tag_loading_status, + tego_tor_bootstrap_tag_loading_keys, + tego_tor_bootstrap_tag_requesting_descriptors, + tego_tor_bootstrap_tag_loading_descriptors, + tego_tor_bootstrap_tag_enough_dirinfo, + tego_tor_bootstrap_tag_ap_conn_pt_summary, + tego_tor_bootstrap_tag_ap_conn_done_pt, + tego_tor_bootstrap_tag_ap_conn_proxy, + tego_tor_bootstrap_tag_ap_conn_done_proxy, + tego_tor_bootstrap_tag_ap_conn, + tego_tor_bootstrap_tag_ap_conn_done, + tego_tor_bootstrap_tag_ap_handshake, + tego_tor_bootstrap_tag_ap_handshake_done, + tego_tor_bootstrap_tag_circuit_create, + tego_tor_bootstrap_tag_done, + + tego_tor_bootstrap_tag_count +} tego_tor_bootstrap_tag_t; + +/* + * Get the summary string associated with the given bootstrap tag + * + * @param tag : the tag to get the summary of + * @param error : filled on error + * @return : utf8 null-terminated summary string, NULL on error + */ +const char* tego_tor_bootstrap_tag_to_summary( + tego_tor_bootstrap_tag_t tag, + tego_error_t** error); + +/* + * Get the current bootstrap status and progress + * + * @param context : the current tego context + * @param out_progress : destination to save progress as a percent, 0 to 100 + * @param out_tag : destination to save bootstrap tag + * @param error : filled on error + */ +void tego_context_get_tor_bootstrap_status( + const tego_context_t* context, + int32_t* out_progress, + tego_tor_bootstrap_tag_t* out_tag, + tego_error_t** error); + +// +// Tego Chat Methods +// + +// milliseconds since 1970-01-01T00:00:00 utc. +typedef uint64_t tego_time_t; +// unique (per user) message identifier +typedef uint32_t tego_message_id_t; +// unique (per user) file transfer identifier +typedef uint32_t tego_file_transfer_id_t; +// struct for file hash +typedef struct tego_file_hash tego_file_hash_t; +// integer type for file size +typedef uint64_t tego_file_size_t; + +/* + * Calculates the number of bytes needed to serialize a file hash to + * a null-terminated utf8 string + * + * @param fileHash : file hash object to serialize + * @param error : filled on error + * @return : the number of bytes required to serialize fileHash including + * the null-terinator + */ +size_t tego_file_hash_string_size( + tego_file_hash_t const* fileHash, + tego_error_t** error); + +/* + * Serializes out a file hash as a null-terminated utf8 string to + * provided character buffer. + * + * @param fileHash : file hash object to serialize + * @param out_hashString : destination buffer to write string + * @param hashStringSize : size of the out_hashString buffer in bytes + * @param error : filled on error + * @return : number of bytes written to out_hashString including the + * null-terminator + */ +size_t tego_file_hash_to_string( + tego_file_hash_t const* fileHash, + char* out_hashString, + size_t hashStringSize, + tego_error_t** error); + +/* + * Send a text message from the host to the given user + * + * @param context : the current tego context + * @param user : the user to send a message to + * @param message : utf8 text message to send + * @param messageLength : length of message not including null-terminator + * @param out_id : filled with assigned message id for callbacks + * @param error : filled on error + */ +void tego_context_send_message( + tego_context_t* context, + const tego_user_id_t* user, + const char* message, + size_t messageLength, + tego_message_id_t* out_id, + tego_error_t** error); + +/* + * Request to send a file to the given user + * + * @param context : the current tego context + * @param user : the user to send a file to + * @param filePath : utf8 path to file to send + * @param filePathLength : length of filePath not including null-terminator + * @param out_id : optional, filled with assigned file transfer id for callbacks + * @param out_fileHash : optional, filled with hash of the file to send + * @param out_fileSize : optional, filled with the size of the file in bytes + * @param error : filled on error + */ +void tego_context_send_file_transfer_request( + tego_context_t* context, + tego_user_id_t const* user, + char const* filePath, + size_t filePathLength, + tego_file_transfer_id_t* out_id, + tego_file_hash_t** out_fileHash, + tego_file_size_t* out_fileSize, + tego_error_t** error); + +typedef enum +{ + tego_file_transfer_response_accept, // proceed with a file transfer + tego_file_transfer_response_reject, // reject the file transfer +} tego_file_transfer_response_t; + +/* + * Acknowledges a request to send an file_transfer + * + * @param context : the current tego context + * @param user : the user that sent the file transfer request + * @param id : which file transfer to respond to + * @param response : how to respond to the request + * @param destPath : optional, destination to save the file + * @param destPathLength : length of destPath not including the null-terminator + * @param error : filled on error + */ +void tego_context_respond_file_transfer_request( + tego_context_t* context, + tego_user_id_t const* user, + tego_file_transfer_id_t id, + tego_file_transfer_response_t response, + char const* destPath, + size_t destPathLength, + tego_error_t** error); + +/* + * Cancel an in-progress file transfer + * + * @param context : the current tego context + * @param user : the user that is sending/receiving the transfer + * @param id : the file transfer to cancel + * @param error: filled on error + */ +void tego_context_cancel_file_transfer( + tego_context_t* context, + tego_user_id_t const* user, + tego_file_transfer_id_t id, + tego_error_t** error); + +/* + * Sends a request to chat to a user + * + * @param context : the current tego context + * @param user : the user we want to chat with + * @param mesage : utf8 text greeting message to send + * @param messageLength : length of message not including null-terminator + * @param error : filled on error + */ +void tego_context_send_chat_request( + tego_context_t* context, + const tego_user_id_t* user, + const char* message, + size_t messageLength, + tego_error_t** error); + +typedef enum +{ + tego_chat_acknowledge_accept, // allows the user to chat with us + tego_chat_acknowledge_reject, // do not allow the user to chat with us + tego_chat_acknowledge_block, // do not allow and reject all future requests +} tego_chat_acknowledge_t; + +/* + * Acknowledges chat request sent from another user. Would be called after receiving + * a chat_request_received callback. + * + * @param context : the current tego context + * @param user : the user that sent the chat request + * @param response : how to respond to the request + * @param error : filled on error + */ +void tego_context_acknowledge_chat_request( + tego_context_t* context, + const tego_user_id_t* user, + tego_chat_acknowledge_t response, + tego_error_t** error); + +/* + * Prevent the given user from message the host + * + * @param context : the current tego context + * @param user : the user to block + * @param error : filled on error + */ +void tego_context_block_user( + tego_context_t* context, + const tego_user_id_t* user, + tego_error_t** error); + +/* + * Forget about a given user, said user will be removed + * from all internal lists and will be needed to be re-added + * to chat + * + * @param context : the current tego context + * @param user : the user to forget + * @param error : filled on error + */ + +void tego_context_forget_user( + tego_context_t* context, + const tego_user_id_t* user, + tego_error_t** error); + +// +// Callbacks for frontend to respond to events +// Provides no guarantees on what thread they are running on or thread safety +// All parameters (such as tego_error_t*) are automatically destroyed after user +// callback is invoked, so duplicate/marshall data as necessary +// + +// TODO: remove the origin param once we better understand how errors are routed through the UI +// temporarily used by the error callback +typedef enum +{ + tego_tor_error_origin_control, + tego_tor_error_origin_manager, +} tego_tor_error_origin_t; + +/* + * Callback fired when an error relating to Tor occurs, unrelated to an existing + * execution context (ie a function being called) + * + * @param context : the current tego context + * @param origin : which legacy Qt component the error came from + * @param error : error containing our message + */ +typedef void (*tego_tor_error_occurred_callback_t)( + tego_context_t* context, + tego_tor_error_origin_t origin, + const tego_error_t* error); + +/* + * TODO: this should go away and only exists for the ricochet Qt UI :( + * saving the daemon config should probably just be synchrynous + * Callback fired after we attempt to save the tor configuration + * + * @param context : the current tego context + * @param out_success : where the result is saved, TEGO_TRUE on success, else TEGO_FALSE + */ +typedef void (*tego_update_tor_daemon_config_succeeded_callback_t)( + tego_context_t* context, + tego_bool_t success); + +/* + * Callback fired when the tor control port status has changed + * + * @param context : the current tego context + * @param status : the new control status + */ +typedef void (*tego_tor_control_status_changed_callback_t)( + tego_context_t* contxt, + tego_tor_control_status_t status); + +/* + * Callback fired when the tor daemon process' status changes + * + * @param context : the current tego context + * @param status : the new process status + */ +typedef void (*tego_tor_process_status_changed_callback_t)( + tego_context_t* context, + tego_tor_process_status_t status); + +/* + * Callback fired when the tor daemon's network status changes + * + * @param context : the current tego context + * @param status : the new network status + */ +typedef void (*tego_tor_network_status_changed_callback_t)( + tego_context_t* context, + tego_tor_network_status_t status); + +/* + * Callback fired when tor's bootstrap status changes + * + * @param context : the current tego context + * @param progress : the bootstrap progress percent + * @param tag : the bootstrap tag + */ +typedef void (*tego_tor_bootstrap_status_changed_callback_t)( + tego_context_t* context, + int32_t progress, + tego_tor_bootstrap_tag_t tag); + +/* + * Callback fired when a log entry is received from the tor daemon + * + * @param context : the current tego context + * @param message : a null-terminated log entry string + * @param messageLength : length of the message not including null-terminator + */ +typedef void (*tego_tor_log_received_callback_t)( + tego_context_t* context, + const char* message, + size_t messageLength); + +/* + * Callback fired when the host user state changes + * + * @param context : the current tego context + * @param state : the current host user state + */ +typedef void (*tego_host_user_state_changed_callback_t)( + tego_context_t* context, + tego_host_user_state_t state); + +/* + * Callback fired when the host receives a chat request from another user + * + * @param context : the current tego context + * @param sender : the user that wants to chat + * @param message : null-terminated message string received from the requesting user + * @param messageLength : length of the message not including null-terminator + */ +typedef void (*tego_chat_request_received_callback_t)( + tego_context_t* context, + const tego_user_id_t* sender, + const char* message, + size_t messageLength); + +/* + * Callback fired when the host receives a response to their sent chat request + * + * @param context : the current tego context + * @param sender : the user responding to our chat request + * @param acceptedRequest : TEGO_TRUE if request accepted, TEGO_FALSE if rejected + */ + +typedef void (*tego_chat_request_response_received_callback_t)( + tego_context_t* context, + const tego_user_id_t* sender, + tego_bool_t acceptedRequest); + + +/* + * Callback fired when the host receives a message from another user + * + * @param context : the current tego context + * @param sender : the user that sent host the message + * @param timestamp : the time the message was sent + * @param messageId : id of the message received + * @param message : null-terminated message string + * @param messageLength : length of the message not including null-terminator + */ +typedef void (*tego_message_received_callback_t)( + tego_context_t* context, + const tego_user_id_t* sender, + tego_time_t timestamp, + tego_message_id_t messageId, + const char* message, + size_t messageLength); + +/* + * Callback fired when a chat message is received and acknowledge + * by the recipient + * + * @param context : the current tego context + * @param userId : the user the message was sent to + * @param messageId : id of the message being acknowledged + * @param messageAcked : TEGO_TRUE if acknowledged, TEGO_FALSE if error + */ +typedef void (*tego_message_acknowledged_callback_t)( + tego_context_t* context, + const tego_user_id_t* userId, + tego_message_id_t messageId, + tego_bool_t messageAcked); + + +/* + * Callback fired when a user wants to send recipient a file + * + * @param context : the current tego context + * @param sender : the user sending the request + * @param id : id of the file transfer received + * @param fileName : name of the file user wants to send + * @param fileNameLength : length of fileName not including the null-terminator + * @param fileSize : size of the file in bytes + * @param fileHash : hash of the file + */ +typedef void (*tego_file_transfer_request_received_callback_t)( + tego_context* context, + tego_user_id_t const* sender, + tego_file_transfer_id_t id, + char const* fileName, + size_t fileNameLength, + tego_file_size_t fileSize, + tego_file_hash_t const* fileHash); + +/* + * Callback fired when a file transfer request message is received and + * acknowledged by the recipient (not whether the recipient wishes to start + * the file transfer) + * + * @param context : the current tego cotext + * @param receiver : the user acknowledging our request + * @param id : the id of the file transfer that is being acknowledged + * @param requestAcked : TEGO_TRUE if acknowledged, TEGO_FALSE if error + */ +typedef void (*tego_file_transfer_request_acknowledged_callback_t)( + tego_context_t* context, + tego_user_id_t const* receiver, + tego_file_transfer_id_t id, + tego_bool_t requestAcked); + +/* + * Callback fired when the user responds to an file transfer request + * + * @param context : the current tego context + * @param receiver : the user accepting or rejecting our request + * @param id : the id of the file transfer that is being accepted + * @param response : TEGO_TRUE if the recipients wants to recevie + * our file, TEGO_FALSE otherwise + */ +typedef void (*tego_file_transfer_request_response_received_callback_t)( + tego_context_t* context, + tego_user_id_t const* receiver, + tego_file_transfer_id_t id, + tego_file_transfer_response_t response); + +typedef enum +{ + tego_file_transfer_direction_sending, + tego_file_transfer_direction_receiving, +} tego_file_transfer_direction_t; + +/* + * Callback fired when file transfer send or receive progress has changed + * This callback is fired for both the sender and the receiver + * + * @param context : the current tego context + * @param userId : the user sending/receiving the file + * @param id : the file transfer associated with this callback + * @param direction : the direction this file is going + * @param bytesComplete : number of bytes sent/received + * @param bytesTotal : the total size of the file + */ +typedef void (*tego_file_transfer_progress_callback_t)( + tego_context_t* context, + const tego_user_id_t* userId, + tego_file_transfer_id_t id, + tego_file_transfer_direction_t direction, + tego_file_size_t bytesComplete, + tego_file_size_t bytesTotal); + +typedef enum +{ + tego_file_transfer_result_success, // file transfer completed successfully + tego_file_transfer_result_failure, // file transfer failed for unknown reason + tego_file_transfer_result_cancelled, // file transfer was cancelled by one of the participants after it had started + tego_file_transfer_result_rejected, // file transfer request was rejected by the receiver + tego_file_transfer_result_bad_hash, // file transfer completed but final file's hash did not match the one advertised + tego_file_transfer_result_network_error, // file transfer failed due to connectivity problem + tego_file_transfer_result_filesystem_error, // file transfer failed due to a file system error +} tego_file_transfer_result_t; + +/* + * Callback fired when a file transfer has completed + * either successfully or in error + * + * @param context : the current tego context + * @param userId : the user sending/receivintg the file + * @param id : the file transfer associated with this callback + * @param direction : the direction this file was going + * @param result : how the transfer completed + */ +typedef void (*tego_file_transfer_complete_callback_t)( + tego_context_t* context, + const tego_user_id_t* userId, + tego_file_transfer_id_t id, + tego_file_transfer_direction_t direction, + tego_file_transfer_result_t result); + +/* + * Callback fired when a user's status changes + * + * @param context : the current tego context + * @param user : the user whose status has changed + * @param status: the user's new status + */ +typedef void (*tego_user_status_changed_callback_t)( + tego_context_t* context, + const tego_user_id_t* user, + tego_user_status_t status); + +/* + * Callback fired when tor creates a new onion service for + * the host + * + * @param context : the current tego context + * @param privateKey : the host's private key + */ +typedef void (*tego_new_identity_created_callback_t)( + tego_context_t* context, + const tego_ed25519_private_key_t* privateKey); + +/* + * Setters for various callbacks + */ + +void tego_context_set_tor_error_occurred_callback( + tego_context_t* context, + tego_tor_error_occurred_callback_t, + tego_error_t** error); + +void tego_context_set_update_tor_daemon_config_succeeded_callback( + tego_context_t* context, + tego_update_tor_daemon_config_succeeded_callback_t, + tego_error_t** error); + +void tego_context_set_tor_control_status_changed_callback( + tego_context_t* context, + tego_tor_control_status_changed_callback_t, + tego_error_t** error); + +void tego_context_set_tor_process_status_changed_callback( + tego_context_t* context, + tego_tor_process_status_changed_callback_t, + tego_error_t** error); + +void tego_context_set_tor_network_status_changed_callback( + tego_context_t* context, + tego_tor_network_status_changed_callback_t, + tego_error_t** error); + +void tego_context_set_tor_bootstrap_status_changed_callback( + tego_context_t* context, + tego_tor_bootstrap_status_changed_callback_t, + tego_error_t** error); + +void tego_context_set_tor_log_received_callback( + tego_context_t* context, + tego_tor_log_received_callback_t, + tego_error_t** error); + +void tego_context_set_host_user_state_changed_callback( + tego_context_t* context, + tego_host_user_state_changed_callback_t, + tego_error_t** error); + +void tego_context_set_chat_request_received_callback( + tego_context_t* context, + tego_chat_request_received_callback_t, + tego_error_t** error); + +void tego_context_set_chat_request_response_received_callback( + tego_context_t* context, + tego_chat_request_response_received_callback_t, + tego_error_t** error); + +void tego_context_set_message_received_callback( + tego_context_t* context, + tego_message_received_callback_t, + tego_error_t** error); + +void tego_context_set_message_acknowledged_callback( + tego_context_t* context, + tego_message_acknowledged_callback_t, + tego_error_t** error); + +void tego_context_set_file_transfer_request_received_callback( + tego_context_t* context, + tego_file_transfer_request_received_callback_t, + tego_error_t** error); + +void tego_context_set_file_transfer_request_acknowledged_callback( + tego_context_t* context, + tego_file_transfer_request_acknowledged_callback_t, + tego_error_t** error); + +void tego_context_set_file_transfer_request_response_received_callback( + tego_context_t* context, + tego_file_transfer_request_response_received_callback_t, + tego_error_t** error); + +void tego_context_set_file_transfer_progress_callback( + tego_context_t* context, + tego_file_transfer_progress_callback_t, + tego_error_t** error); + +void tego_context_set_file_transfer_complete_callback( + tego_context_t* context, + tego_file_transfer_complete_callback_t, + tego_error_t** error); + +void tego_context_set_user_status_changed_callback( + tego_context_t* context, + tego_user_status_changed_callback_t, + tego_error_t** error); + +void tego_context_set_new_identity_created_callback( + tego_context_t* context, + tego_new_identity_created_callback_t, + tego_error_t** error); + + +/* + Destructors for various tego types + */ + +// error +void tego_error_delete(tego_error_t*); + +// crypto +void tego_ed25519_private_key_delete(tego_ed25519_private_key_t*); +void tego_ed25519_public_key_delete(tego_ed25519_public_key_t*); +void tego_ed25519_signature_delete(tego_ed25519_signature_t*); +void tego_v3_onion_service_id_delete(tego_v3_onion_service_id_t*); + +// user +void tego_user_id_delete(tego_user_id_t*); + +// tor +void tego_tor_launch_config_delete(tego_tor_launch_config_t*); +void tego_tor_daemon_config_delete(tego_tor_daemon_config_t*); + +// file transfer +void tego_file_hash_delete(tego_file_hash_t*); + +#ifdef __cplusplus +} +#endif // __cplusplus +#endif // TEGO_H \ No newline at end of file diff --git a/src/libtego/include/tego/tego.hpp b/src/libtego/include/tego/tego.hpp new file mode 100644 index 00000000..94d95834 --- /dev/null +++ b/src/libtego/include/tego/tego.hpp @@ -0,0 +1,87 @@ +#pragma once +// C + +// libtego +#include + +// C++ + +// standard library +#include +#include +#include +#include +#include + +// libtego +#include +//#define ENABLE_TEGO_LOGGER +#include + +namespace tego +{ + // + // converts tego_error_t** C style error handling to exceptions + // + class throw_on_error + { + public: + ~throw_on_error() noexcept(false) + { + if (error_ != nullptr) + { + logger::println("exception thrown : {}", tego_error_get_message(error_)); + std::runtime_error ex(tego_error_get_message(error_)); + tego_error_delete(error_); + error_ = nullptr; + throw ex; + } + } + + operator tego_error_t**() + { + return &error_; + } + private: + tego_error_t* error_ = nullptr; + }; + + // + // to_string methods to convert various tego types to human readable strings + // + inline std::string to_string(tego_file_hash_t const* fileHash) + { + + if (fileHash == nullptr) return {}; + + // size of string including null terminator + const auto hashSize = tego_file_hash_string_size(fileHash, tego::throw_on_error()); + + // std::string expects length as arg, not buffer size + std::string hashString(hashSize-1, 0); + tego_file_hash_to_string(fileHash, const_cast(hashString.data()), hashSize, tego::throw_on_error()); + + return hashString; + } +} + + +// define deleters for using unique_ptr and shared_ptr with tego types + +#define TEGO_DEFAULT_DELETE_IMPL(TYPE)\ +namespace std {\ + template<> class default_delete {\ + public:\ + void operator()(TYPE##_t* val) { TYPE##_delete(val); }\ + };\ +} + +TEGO_DEFAULT_DELETE_IMPL(tego_ed25519_private_key) +TEGO_DEFAULT_DELETE_IMPL(tego_ed25519_public_key) +TEGO_DEFAULT_DELETE_IMPL(tego_ed25519_signature) +TEGO_DEFAULT_DELETE_IMPL(tego_v3_onion_service_id) +TEGO_DEFAULT_DELETE_IMPL(tego_tor_launch_config) +TEGO_DEFAULT_DELETE_IMPL(tego_tor_daemon_config) +TEGO_DEFAULT_DELETE_IMPL(tego_user_id) +TEGO_DEFAULT_DELETE_IMPL(tego_file_hash) + diff --git a/src/libtego/include/tego/utilities.hpp b/src/libtego/include/tego/utilities.hpp new file mode 100644 index 00000000..73906f85 --- /dev/null +++ b/src/libtego/include/tego/utilities.hpp @@ -0,0 +1,101 @@ +#pragma once + +namespace tego +{ + // + // call functor at end of scope + // + template + class scope_exit + { + public: + scope_exit() = delete; + scope_exit(const scope_exit&) = delete; + scope_exit& operator=(const scope_exit&) = delete; + scope_exit& operator=(scope_exit&&) = delete; + + scope_exit(scope_exit&&) = default; + scope_exit(T&& functor) + : functor_(new T(std::move(functor))) + { + static_assert(std::is_same::value); + } + + ~scope_exit() + { + if (functor_.get()) + { + functor_->operator()(); + } + } + + private: + std::unique_ptr functor_; + }; + + + template + auto make_scope_exit(FUNC&& func) -> + scope_exit::type> + { + return {std::move(func)}; + } + + // + // constexpr strlen for compile-time null terminated C String constants + // + template + constexpr size_t static_strlen(const char (&str)[N]) + { + if (str[N-1] != 0) throw "C String missing null terminator"; + for(size_t i = 0; i < (N - 1); i++) + { + if (str[i] == 0) throw "C String has early null terminator"; + } + return N-1; + } + + // + // helper class for populating out T** params into unique_ptr objects + // + template + class out_unique_ptr + { + public: + out_unique_ptr() = delete; + out_unique_ptr(const out_unique_ptr&) = delete; + out_unique_ptr(out_unique_ptr&&) = delete; + out_unique_ptr& operator=(const out_unique_ptr&) = delete; + out_unique_ptr& operator=(out_unique_ptr&&) = delete; + + out_unique_ptr(std::unique_ptr& u) : u_(u) {} + ~out_unique_ptr() + { + u_.reset(t_); + } + + operator T**() + { + return &t_; + } + + private: + T* t_ = nullptr; + std::unique_ptr& u_; + }; + + // + // helper function for populating out T** params + // example: + // + // void give_int(int** outInt); + // std::unique_ptr pint; + // give_int(tego::out(pint)); + // int val = *pint; + // + template + out_unique_ptr out(std::unique_ptr& ptr) + { + return {ptr}; + } +} diff --git a/src/libtego/source/context.cpp b/src/libtego/source/context.cpp new file mode 100644 index 00000000..5ffeee73 --- /dev/null +++ b/src/libtego/source/context.cpp @@ -0,0 +1,1211 @@ +#include "context.hpp" +#include "error.hpp" +#include "globals.hpp" +#include "tor.hpp" +#include "user.hpp" +#include "ed25519.hpp" + +using tego::g_globals; + +#include "tor/TorControl.h" +#include "tor/TorManager.h" +#include "tor/TorProcess.h" +#include "core/UserIdentity.h" +#include "core/ContactUser.h" +#include "core/ConversationModel.h" + +// +// Tego Context +// + +tego_context::tego_context() +: callback_registry_(this) +, callback_queue_(this) +, threadId(std::this_thread::get_id()) +{ + this->torManager = Tor::TorManager::instance(); + this->torControl = torManager->control(); +} + +void tego_context::start_tor(const tego_tor_launch_config_t* config) +{ + TEGO_THROW_IF_NULL(this->torManager); + TEGO_THROW_IF_NULL(config); + + this->torManager->setDataDirectory(config->dataDirectory.data()); + this->torManager->start(); +} + +bool tego_context::get_tor_daemon_configured() const +{ + TEGO_THROW_IF_NULL(this->torManager); + + return !this->torManager->configurationNeeded(); +} + +size_t tego_context::get_tor_logs_size() const +{ + size_t retval = 0; + for(const auto& msg : this->get_tor_logs()) + { + retval += msg.size() + 1; + } + return retval; +} + +const std::vector& tego_context::get_tor_logs() const +{ + TEGO_THROW_IF_NULL(this->torManager); + + auto logMessages = this->torManager->logMessages(); + TEGO_THROW_IF_FALSE(logMessages.size() >= 0); + + // see if we need to update our local copy + if(static_cast(logMessages.size()) != this->torLogs.size()) + { + for(size_t i = this->torLogs.size(); i < static_cast(logMessages.size()); i++) + { + TEGO_THROW_IF_FALSE(i < std::numeric_limits::max()); + this->torLogs.push_back(logMessages[static_cast(i)].toStdString()); + } + } + + return this->torLogs; +} + +const char* tego_context::get_tor_version_string() const +{ + if (torVersion.empty()) + { + TEGO_THROW_IF_NULL(this->torControl); + + QString version = this->torControl->torVersion(); + this->torVersion = version.toStdString(); + } + + return this->torVersion.c_str(); +} + +tego_tor_control_status_t tego_context::get_tor_control_status() const +{ + TEGO_THROW_IF_NULL(this->torControl); + return static_cast(this->torControl->status()); +} + +tego_tor_process_status_t tego_context::get_tor_process_status() const +{ + TEGO_THROW_IF_NULL(this->torManager); + + auto torProcess = this->torManager->process(); + if (torProcess == nullptr) + { + return tego_tor_process_status_external; + } + + switch(torProcess->state()) + { + case Tor::TorProcess::Failed: + return tego_tor_process_status_failed; + case Tor::TorProcess::NotStarted: + return tego_tor_process_status_not_started; + case Tor::TorProcess::Starting: + return tego_tor_process_status_starting; + case Tor::TorProcess::Connecting: + // fall through, Connecting is really control status and not used by frontend explicitly + case Tor::TorProcess::Ready: + return tego_tor_process_status_running; + } + + return tego_tor_process_status_unknown; +} + +tego_tor_network_status_t tego_context::get_tor_network_status() const +{ + TEGO_THROW_IF_NULL(this->torControl); + switch(this->torControl->torStatus()) + { + case Tor::TorControl::TorOffline: + return tego_tor_network_status_offline; + case Tor::TorControl::TorReady: + return tego_tor_network_status_ready; + default: + return tego_tor_network_status_unknown; + } +} + +tego_tor_bootstrap_tag_t tego_context::get_tor_bootstrap_tag() const +{ + TEGO_THROW_IF_NULL(this->torControl); + auto bootstrapStatus = this->torControl->bootstrapStatus(); + auto bootstrapTag = bootstrapStatus["tag"].toString(); + + // see https://gitweb.torproject.org/torspec.git/tree/control-spec.txt#n3867 + // TODO: optimize this function if you're bored, could do binary search rather than linear search + constexpr static const char* tagList[] = + { + "starting", + "conn_pt", + "conn_done_pt", + "conn_proxy", + "conn_done_proxy", + "conn", + "conn_done", + "handshake", + "handshake_done", + "onehop_create", + "requesting_status", + "loading_status", + "loading_keys", + "requesting_descriptors", + "loading_descriptors", + "enough_dirinfo", + "ap_conn_pt_summary", + "ap_conn_done_pt", + "ap_conn_proxy", + "ap_conn_done_proxy", + "ap_conn", + "ap_conn_done", + "ap_handshake", + "ap_handshake_done", + "circuit_create", + "done", + }; + static_assert(tego::countof(tagList) == tego_tor_bootstrap_tag_count); + + for(size_t i = 0; i < tego_tor_bootstrap_tag_count; i++) + { + if (tagList[i] == bootstrapTag) + { + return static_cast(i); + } + } + + TEGO_THROW_MSG("unrecognized bootstrap tag : \"{}\"", bootstrapTag); +} + +void tego_context::start_service( + tego_ed25519_private_key_t const* hostPrivateKey, + tego_user_id_t const* const* userBuffer, + tego_user_type_t* const userTypeBuffer, + size_t userCount) +{ + TEGO_THROW_IF_NULL(hostPrivateKey); + if (userCount > 0) + { + TEGO_THROW_IF_NULL(userBuffer); + TEGO_THROW_IF_NULL(userTypeBuffer); + } + else + { + TEGO_THROW_IF_NOT_NULL(userBuffer); + TEGO_THROW_IF_NOT_NULL(userTypeBuffer); + } + + char rawKeyBlob[TEGO_ED25519_KEYBLOB_SIZE] = {0}; + tego_ed25519_keyblob_from_ed25519_private_key( + rawKeyBlob, + sizeof(rawKeyBlob), + hostPrivateKey, + tego::throw_on_error()); + + auto keyBlob = QString::fromUtf8(rawKeyBlob, TEGO_ED25519_KEYBLOB_LENGTH); + + // our different types of users + QList allowedUsers; + QList requestingUsers; + QList blockedUsers; + QList pendingUsers; + QList rejectedUsers; + + for(size_t k = 0; k < userCount; k++) + { + const auto userType = userTypeBuffer[k]; + const auto userHostname = QString::fromUtf8(userBuffer[k]->serviceId.data, TEGO_V3_ONION_SERVICE_ID_LENGTH) + ".onion"; + + switch(userTypeBuffer[k]) + { + case tego_user_type_host: + TEGO_THROW_MSG("passed in userTypeBuffer[{}] is invalid type 'tego_user_type_host'", k); + break; + case tego_user_type_allowed: + allowedUsers.push_back(userHostname); + break; + case tego_user_type_requesting: + requestingUsers.push_back(userHostname); + break; + case tego_user_type_blocked: + blockedUsers.push_back(userHostname); + break; + case tego_user_type_pending: + pendingUsers.push_back(userHostname); + break; + case tego_user_type_rejected: + rejectedUsers.push_back(userHostname); + break; + default: + TEGO_THROW_MSG("passed in userTypeBuffer[{}] : ({}) is invalid", k, static_cast(userType)); + break; + } + } + + // save off the singleton on our context + this->identityManager = new IdentityManager(keyBlob); + auto userIdentity = this->identityManager->identities().first(); + auto contactsManager = userIdentity->getContacts(); + + contactsManager->addAllowedContacts(allowedUsers); + contactsManager->addIncomingRequests(requestingUsers); + contactsManager->addRejectedIncomingRequests(blockedUsers); + contactsManager->addOutgoingRequests(pendingUsers); + contactsManager->addRejectedOutgoingRequests(rejectedUsers); +} + +void tego_context::start_service() +{ + this->identityManager = new IdentityManager({}, {}); +} + +int32_t tego_context::get_tor_bootstrap_progress() const +{ + TEGO_THROW_IF_NULL(this->torControl); + auto bootstrapStatus = this->torControl->bootstrapStatus(); + auto bootstrapProgress = bootstrapStatus["progress"].toInt(); + return static_cast(bootstrapProgress); +} + +void tego_context::update_tor_daemon_config(const tego_tor_daemon_config_t* daemonConfig) +{ + TEGO_THROW_IF_NULL(this->torControl); + + const auto& config = *daemonConfig; + + QVariantMap vm; + + // init the tor settings we can modify here + constexpr static auto configKeys = + { + "DisableNetwork", + "Socks4Proxy", + "Socks5Proxy", + "Socks5ProxyUsername", + "Socks5ProxyPassword", + "HTTPSProxy", + "HTTPSProxyAuthenticator", + "ReachableAddresses", + "Bridge", + "UseBridges", + }; + for(const auto& currentKey : configKeys) + { + vm[currentKey] = ""; + } + + // set disable network flag + if (config.disableNetwork.has_value()) { + vm["DisableNetwork"] = (config.disableNetwork.value() ? "1" : "0"); + } + + // set proxy info + switch(config.proxy.type) + { + case tego_proxy_type_none: break; + case tego_proxy_type_socks4: + { + vm["Socks4Proxy"] = QString::fromStdString(fmt::format("{}:{}", config.proxy.address, config.proxy.port)); + } + break; + case tego_proxy_type_socks5: + { + vm["Socks5Proxy"] = QString::fromStdString(fmt::format("{}:{}", config.proxy.address, config.proxy.port)); + if (!config.proxy.username.empty()) + { + vm["Socks5ProxyUsername"] = QString::fromStdString(config.proxy.username); + } + if (!config.proxy.password.empty()) + { + vm["Socks5ProxyPassword"] = QString::fromStdString(config.proxy.password); + } + } + break; + case tego_proxy_type_https: + { + vm["HTTPSProxy"] = QString::fromStdString(fmt::format("{}:{}", config.proxy.address, config.proxy.port)); + if (!config.proxy.username.empty() || !config.proxy.password.empty()) + { + vm["HTTPSProxyAuthenticator"] = QString::fromStdString(fmt::format("{}:{}", config.proxy.username, config.proxy.password)); + } + } + break; + } + + // set firewall ports + if (config.allowedPorts.size() > 0) + { + std::stringstream ss; + + auto it = config.allowedPorts.begin(); + + ss << fmt::format("*:{}", *it); + for(++it; it < config.allowedPorts.end(); ++it) + { + ss << fmt::format(", *:{}", *it); + } + + vm["ReachableAddresses"] = QString::fromStdString(ss.str()); + } + + // set bridges + if (config.bridges.size() > 0) + { + QVariantList bridges; + for(const auto& currentBridge : config.bridges) + { + bridges.append(QString::fromStdString(currentBridge)); + } + vm["Bridge"] = bridges; + vm["UseBridges"] = "1"; + } + + this->torControl->setConfiguration(vm); +} + +void tego_context::save_tor_daemon_config() +{ + TEGO_THROW_IF_NULL(this->torControl); + logger::trace(); + this->torControl->saveConfiguration(); +} + +void tego_context::set_host_user_state(tego_host_user_state_t state) +{ + if (state == hostUserState) + { + return; + } + + this->hostUserState = state; + this->callback_registry_.emit_host_user_state_changed(state); +} + +std::unique_ptr tego_context::get_host_user_id() const +{ + TEGO_THROW_IF_NULL(this->identityManager); + auto userIdentity = this->identityManager->identities().first(); + + auto hostname = userIdentity->hostname().toUtf8(); + tego_v3_onion_service_id serviceId(hostname.data(), TEGO_V3_ONION_SERVICE_ID_LENGTH); + + return std::make_unique(serviceId); +} + +tego_host_user_state_t tego_context::get_host_user_state() const +{ + return this->hostUserState; +} + +void tego_context::send_chat_request( + const tego_user_id_t* user, + const char* message, + size_t messageLength) +{ + TEGO_THROW_IF_NULL(this->identityManager); + auto userIdentity = this->identityManager->identities().first(); + auto contactsManager = userIdentity->getContacts(); + + TEGO_THROW_IF_FALSE(messageLength < std::numeric_limits::max()); + contactsManager->createContactRequest( + QString::fromStdString(fmt::format("ricochet:{}", user->serviceId.data)), + (messageLength == 0) ? QString() : QString::fromUtf8(message, static_cast(messageLength))); +} + +void tego_context::acknowledge_chat_request( + const tego_user_id_t* user, + tego_chat_acknowledge_t response) +{ + TEGO_THROW_IF_NULL(user); + + logger::println("ack chat request from {}", user->serviceId.data); + logger::println("response : {}", static_cast(response)); + + TEGO_THROW_IF_NULL(this->identityManager); + auto userIdentity = this->identityManager->identities().first(); + auto contactsManager = userIdentity->getContacts(); + auto incomingRequestManager = contactsManager->incomingRequestManager(); + + auto hostname = QString("%1.onion").arg(user->serviceId.data).toUtf8(); + + auto incomingContactRequest = incomingRequestManager->requestFromHostname(hostname); + TEGO_THROW_IF_NULL(incomingContactRequest); + + switch(response) + { + case tego_chat_acknowledge_accept: + incomingContactRequest->accept(nullptr); + break; + case tego_chat_acknowledge_reject: + incomingContactRequest->reject(); + break; + case tego_chat_acknowledge_block: + incomingContactRequest->reject(); + incomingRequestManager->addRejectedHost(hostname); + break; + } +} + +tego_message_id_t tego_context::send_message( + const tego_user_id_t* user, + const std::string& message) +{ + TEGO_THROW_IF_NULL(user); + TEGO_THROW_IF_FALSE(message.size() > 0) + + auto contactUser = getContactUser(user); + TEGO_THROW_IF_NULL(contactUser); + auto conversationModel = contactUser->conversation(); + + return conversationModel->sendMessage(QString::fromStdString(message)); +} + +tego_user_type_t tego_context::get_user_type(tego_user_id_t const* user) const +{ + auto contactUser = this->getContactUser(user); + if (contactUser != nullptr) + { + auto const status = contactUser->status(); + switch(status) + { + case ContactUser::Online: + case ContactUser::Offline: + return tego_user_type_allowed; + case ContactUser::RequestPending: + return tego_user_type_pending; + case ContactUser::RequestRejected: + return tego_user_type_rejected; + default: + TEGO_THROW_MSG("Unknown ContactUser::Status : {}", static_cast(status)); + break; + } + } + + // next check for requesting users + auto userIdentity = this->identityManager->identities().first(); + auto contactsManager = userIdentity->getContacts(); + auto incomingRequestManager = contactsManager->incomingRequestManager(); + + auto hostname = QByteArray::fromStdString(fmt::format("{}.onion", user->serviceId.data)); + + // search for the user's serviceId on the incoming request manager + auto contactRequest = incomingRequestManager->requestFromHostname(hostname); + if (contactRequest != nullptr) + { + return tego_user_type_requesting; + } + + // see if the user is blocked + if(incomingRequestManager->isHostnameRejected(hostname)) + { + return tego_user_type_blocked; + } + + // finally see if the user matches the host + if (hostname == userIdentity->hostname().toUtf8()) + { + return tego_user_type_host; + } + + TEGO_THROW_MSG("Unknown user with service id : '{}'", user->serviceId.data); +} + +size_t tego_context::get_user_count() const +{ + TEGO_THROW_IF_NULL(this->identityManager); + auto userIdentity = this->identityManager->identities().first(); + auto contactsManager = userIdentity->getContacts(); + + return static_cast(contactsManager->contacts().size()); +} + +std::vector tego_context::get_users() const +{ + TEGO_THROW_IF_NULL(this->identityManager); + auto userIdentity = this->identityManager->identities().first(); + auto contactsManager = userIdentity->getContacts(); + auto incomingRequestManager = contactsManager->incomingRequestManager(); + + std::vector> managedUsers; + std::vector users; + + // helper function to from hostname to tego_user_id_t + auto hostnameToTegoUserId = [](QString const& hostname) -> std::unique_ptr + { + // convert our hostname to just the service id raw string + auto serviceIdString = hostname.left(TEGO_V3_ONION_SERVICE_ID_LENGTH).toUtf8(); + // ensure valid service id + auto serviceId = std::make_unique(serviceIdString.data(), serviceIdString.size()); + // create user id object from service id + auto userId = std::make_unique(*serviceId.get()); + + return userId; + }; + + // first iterate through our explicit users + for(auto contactUser : contactsManager->contacts()) + { + auto userId = hostnameToTegoUserId(contactUser->hostname()); + + users.push_back(userId.get()); + managedUsers.push_back(std::move(userId)); + } + + // next add our implicit 'incomingContactRequest' users + for(auto incomingContactRequest : incomingRequestManager->requests()) + { + auto userId = hostnameToTegoUserId(incomingContactRequest->hostname()); + + users.push_back(userId.get()); + managedUsers.push_back(std::move(userId)); + } + + // next add our blocked users + for(auto rejectedHostname : incomingRequestManager->getRejectedHostnames()) + { + auto userId = hostnameToTegoUserId(rejectedHostname); + + users.push_back(userId.get()); + managedUsers.push_back(std::move(userId)); + } + + // we got this far, release these ptrs from memory management + for(auto& mu : managedUsers) + { + mu.release(); + } + + return users; +} + +void tego_context::forget_user(const tego_user_id_t* user) +{ + // TODO: does not handle our blocked users or incoming request users + auto contactUser = this->getContactUser(user); + TEGO_THROW_IF_NULL(contactUser); + contactUser->deleteContact(); +} + +std::tuple, tego_file_size_t> tego_context::send_file_transfer_request( + tego_user_id_t const* user, + std::string const& filePath) +{ + TEGO_THROW_IF_NULL(user); + + auto contactUser = this->getContactUser(user); + TEGO_THROW_IF_NULL(contactUser); + auto conversationModel = contactUser->conversation(); + + return conversationModel->sendFile(QString::fromStdString(filePath)); +} + +void tego_context::respond_file_transfer_request( + tego_user_id_t const* user, + tego_file_transfer_id_t fileTransfer, + tego_file_transfer_response_t response, + std::string const& destPath) +{ + // ensure we have a valid user + TEGO_THROW_IF_NULL(user); + // ensure a valid response + TEGO_THROW_IF_FALSE(response == tego_file_transfer_response_accept || + response == tego_file_transfer_response_reject); + // ensure non-empty dest path in case we are accepting + TEGO_THROW_IF_TRUE(response == tego_file_transfer_response_accept && destPath.empty()) + + auto contactUser = this->getContactUser(user); + TEGO_THROW_IF_NULL(contactUser); + auto conversationModel = contactUser->conversation(); + + switch(response) + { + case tego_file_transfer_response_accept: + { + conversationModel->acceptFile(fileTransfer, destPath); + } + break; + case tego_file_transfer_response_reject: + { + conversationModel->rejectFile(fileTransfer); + } + break; + } +} + +void tego_context::cancel_file_transfer_transfer( + tego_user_id_t const* user, + tego_file_transfer_id_t fileTransfer) +{ + // ensure we have a valid user + TEGO_THROW_IF_NULL(user); + + auto contactUser = this->getContactUser(user); + TEGO_THROW_IF_NULL(contactUser); + auto conversationModel = contactUser->conversation(); + + conversationModel->cancelTransfer(fileTransfer); +} + +// +// tego_context private methods +// + +ContactUser* tego_context::getContactUser(tego_user_id_t const* user) const +{ + TEGO_THROW_IF_NULL(user); + TEGO_THROW_IF_NULL(identityManager); + + auto contactsManager = identityManager->identities().first()->getContacts(); + auto contactUser = contactsManager->lookupHostname( + QString::fromUtf8( + user->serviceId.data, + TEGO_V3_ONION_SERVICE_ID_LENGTH)); + + return contactUser; +} + +// +// Exports +// + +extern "C" +{ + // Bootstrap Tag + + const char* tego_tor_bootstrap_tag_to_summary( + tego_tor_bootstrap_tag_t tag, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> const char* + { + TEGO_THROW_IF_FALSE(tag > tego_tor_bootstrap_tag_invalid && + tag < tego_tor_bootstrap_tag_count); + + // strings from: https://gitweb.torproject.org/torspec.git/tree/control-spec.txt#n3867 + constexpr static const char* summaryList[] = + { + "Starting", + "Connecting to pluggable transport", + "Connected to pluggable transport", + "Connecting to proxy", + "Connected to proxy", + "Connecting to a relay", + "Connected to a relay", + "Handshaking with a relay", + "Handshake with a relay done", + "Establishing an encrypted directory connection", + "Asking for networkstatus consensus", + "Loading networkstatus consensus", + "Loading authority key certs", + "Asking for relay descriptors", + "Loading relay descriptors", + "Loaded enough directory info to build circuits", + "Connecting to pluggable transport to build circuits", + "Connected to pluggable transport to build circuits", + "Connecting to proxy to build circuits", + "Connected to proxy to build circuits", + "Connecting to a relay to build circuits", + "Connected to a relay to build circuits", + "Finishing handshake with a relay to build circuits", + "Handshake finished with a relay to build circuits", + "Establishing a Tor circuit", + "Done", + }; + static_assert(tego::countof(summaryList) == tego_tor_bootstrap_tag_count); + + return summaryList[static_cast(tag)]; + }, error, nullptr); + } + + // Tego Context + + void tego_context_start_tor( + tego_context_t* context, + const tego_tor_launch_config_t* launchConfig, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(context); + TEGO_THROW_IF_FALSE(context->threadId == std::this_thread::get_id()); + + context->start_tor(launchConfig); + }, error); + } + + void tego_context_get_tor_daemon_configured( + const tego_context_t* context, + tego_bool_t* out_configured, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(context); + TEGO_THROW_IF_FALSE(context->threadId == std::this_thread::get_id()); + TEGO_THROW_IF_NULL(out_configured); + + *out_configured = TEGO_FALSE; + + if(context->get_tor_daemon_configured()) + { + *out_configured = TEGO_TRUE; + } + }, error); + } + + size_t tego_context_get_tor_logs_size( + const tego_context_t* context, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> size_t + { + TEGO_THROW_IF_NULL(context); + TEGO_THROW_IF_FALSE(context->threadId == std::this_thread::get_id()); + + return context->get_tor_logs_size(); + }, error, 0); + } + + + size_t tego_context_get_tor_logs( + const tego_context_t* context, + char* out_logBuffer, + size_t logBufferSize, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> size_t + { + TEGO_THROW_IF_NULL(context); + TEGO_THROW_IF_FALSE(context->threadId == std::this_thread::get_id()); + TEGO_THROW_IF_NULL(out_logBuffer); + + // nothing to do if no space to write + if (logBufferSize == 0) + { + return 0; + } + + // get our tor logs + const auto& logs = context->get_tor_logs(); + size_t logsSize = context->get_tor_logs_size(); + + // create temporary buffer to copy each line into + std::vector logBuffer; + logBuffer.reserve(logsSize); + + // copy each log and separate by new lines '\n' + for(const auto& line : logs) + { + std::copy(line.begin(), line.end(), std::back_inserter(logBuffer)); + logBuffer.push_back('\n'); + } + // append null terminator + logBuffer.push_back(0); + + // finally copy at most logBufferSize bytes from logBuffer + size_t copyCount = std::min(logBufferSize, logBuffer.size()); + TEGO_THROW_IF_FALSE(copyCount > 0); + + // check that we won't overflow + TEGO_THROW_IF_FALSE(copyCount < std::numeric_limits::max()); + + std::copy(logBuffer.begin(), logBuffer.begin() + static_cast(copyCount), out_logBuffer); + // always write null terminator at the end + out_logBuffer[copyCount - 1] = 0; + + return copyCount; + }, error, 0); + } + + const char* tego_context_get_tor_version_string( + const tego_context_t* context, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> const char* + { + TEGO_THROW_IF_NULL(context); + TEGO_THROW_IF_FALSE(context->threadId == std::this_thread::get_id()); + + return context->get_tor_version_string(); + }, error, nullptr); + } + + void tego_context_get_tor_control_status( + const tego_context_t* context, + tego_tor_control_status_t* out_status, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(context); + TEGO_THROW_IF_FALSE(context->threadId == std::this_thread::get_id()); + TEGO_THROW_IF_NULL(out_status); + + auto status = context->get_tor_control_status(); + *out_status = status; + }, error); + } + + void tego_context_get_tor_process_status( + const tego_context_t* context, + tego_tor_process_status_t* out_status, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(context); + TEGO_THROW_IF_FALSE(context->threadId == std::this_thread::get_id()); + TEGO_THROW_IF_NULL(out_status); + + const auto status = context->get_tor_process_status(); + *out_status = status; + }, error); + } + + void tego_context_get_tor_network_status( + const tego_context_t* context, + tego_tor_network_status_t* out_status, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(context); + TEGO_THROW_IF_FALSE(context->threadId == std::this_thread::get_id()); + TEGO_THROW_IF_NULL(out_status); + + auto status = context->get_tor_network_status(); + *out_status = status; + }, error); + } + + void tego_context_get_tor_bootstrap_status( + const tego_context* context, + int32_t* out_progress, + tego_tor_bootstrap_tag_t* out_tag, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(context); + TEGO_THROW_IF_FALSE(context->threadId == std::this_thread::get_id()); + TEGO_THROW_IF_NULL(out_progress); + TEGO_THROW_IF_NULL(out_tag); + + auto progress = context->get_tor_bootstrap_progress(); + auto tag = context->get_tor_bootstrap_tag(); + + *out_progress = progress; + *out_tag = tag; + }, error); + } + + void tego_context_start_service( + tego_context_t* context, + tego_ed25519_private_key_t const* hostPrivateKey, + tego_user_id_t const* const* userBuffer, + tego_user_type_t* const userTypeBuffer, + size_t userCount, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(context); + TEGO_THROW_IF_FALSE(context->threadId == std::this_thread::get_id()); + + if (hostPrivateKey == nullptr) + { + TEGO_THROW_IF_FALSE(userBuffer == nullptr && userTypeBuffer == nullptr && userCount == 0); + context->start_service(); + } + else + { + TEGO_THROW_IF_FALSE((userBuffer == nullptr && userTypeBuffer == nullptr && userCount == 0) || + (userBuffer != nullptr && userTypeBuffer != nullptr && userCount > 0)); + + context->start_service( + hostPrivateKey, + userBuffer, + userTypeBuffer, + userCount); + } + }, error); + } + + void tego_context_get_host_user_id( + const tego_context_t* context, + tego_user_id_t** out_hostUser, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(context); + TEGO_THROW_IF_FALSE(context->threadId == std::this_thread::get_id()); + TEGO_THROW_IF_NULL(out_hostUser); + + auto hostUser = context->get_host_user_id(); + *out_hostUser = hostUser.release(); + }, error); + } + + void tego_context_get_host_user_state( + const tego_context_t* context, + tego_host_user_state_t* out_state, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(context); + TEGO_THROW_IF_FALSE(context->threadId == std::this_thread::get_id()); + TEGO_THROW_IF_NULL(out_state); + + auto state = context->get_host_user_state(); + *out_state = state; + }, error); + } + + void tego_context_update_tor_daemon_config( + tego_context_t* context, + const tego_tor_daemon_config_t* torConfig, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(context); + TEGO_THROW_IF_FALSE(context->threadId == std::this_thread::get_id()); + + context->update_tor_daemon_config(torConfig); + }, error); + } + + void tego_context_save_tor_daemon_config( + tego_context_t* context, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(context); + TEGO_THROW_IF_FALSE(context->threadId == std::this_thread::get_id()); + + context->save_tor_daemon_config(); + }, error); + } + + void tego_context_get_user_type( + const tego_context_t* context, + const tego_user_id_t* user, + tego_user_type_t* out_type, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(context); + TEGO_THROW_IF_FALSE(context->threadId == std::this_thread::get_id()); + TEGO_THROW_IF_NULL(user); + TEGO_THROW_IF_NULL(out_type); + + auto type = context->get_user_type(user); + *out_type = type; + }, error); + } + + void tego_context_get_user_count( + const tego_context_t* context, + size_t* out_userCount, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(context); + TEGO_THROW_IF_FALSE(context->threadId == std::this_thread::get_id()); + TEGO_THROW_IF_NULL(out_userCount); + + auto count = context->get_user_count(); + *out_userCount = count; + }, error); + } + + void tego_context_get_users( + const tego_context_t* context, + tego_user_id_t** out_usersBuffer, + size_t usersBufferLength, + size_t* out_userCount, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(context); + TEGO_THROW_IF_FALSE(context->threadId == std::this_thread::get_id()); + TEGO_THROW_IF_NULL(out_usersBuffer); + TEGO_THROW_IF_NULL(out_userCount); + + auto users = context->get_users(); + const auto userCount = std::min(users.size(), usersBufferLength); + for(size_t i = 0; i < userCount; ++i) + { + out_usersBuffer[i] = users[i]; + } + *out_userCount = userCount; + + }, error); + } + + void tego_context_send_chat_request( + tego_context_t* context, + const tego_user_id_t* user, + const char* message, + size_t messageLength, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(context); + TEGO_THROW_IF_FALSE(context->threadId == std::this_thread::get_id()); + TEGO_THROW_IF_NULL(user); + TEGO_THROW_IF_FALSE(message != nullptr || messageLength == 0); + + context->send_chat_request(user, message, messageLength); + }, error); + } + + void tego_context_acknowledge_chat_request( + tego_context_t* context, + const tego_user_id_t* user, + tego_chat_acknowledge_t response, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(context); + TEGO_THROW_IF_FALSE(context->threadId == std::this_thread::get_id()); + + context->acknowledge_chat_request(user, response); + }, error); + } + + void tego_context_send_file_transfer_request( + tego_context* context, + tego_user_id_t const* user, + char const* filePath, + size_t filePathLength, + tego_file_transfer_id_t* out_id, + tego_file_hash_t** out_fileHash, + tego_file_size_t* out_fileSize, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(context); + TEGO_THROW_IF_FALSE(context->threadId == std::this_thread::get_id()); + TEGO_THROW_IF_NULL(user); + TEGO_THROW_IF_NULL(filePath); + TEGO_THROW_IF_FALSE(filePathLength > 0); + + auto [id, fileHash, fileSize] = + context->send_file_transfer_request( + user, + std::string(filePath, filePathLength)); + + if (out_id != nullptr) + { + *out_id = id; + } + if (out_fileHash != nullptr) + { + *out_fileHash = fileHash.release(); + } + if (out_fileSize != nullptr) + { + *out_fileSize = fileSize; + } + + }, error); + } + + void tego_context_respond_file_transfer_request( + tego_context* context, + tego_user_id_t const* user, + tego_file_transfer_id_t fileTransfer, + tego_file_transfer_response_t response, + char const* destPath, + size_t destPathLength, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(context); + TEGO_THROW_IF_FALSE(context->threadId == std::this_thread::get_id()); + TEGO_THROW_IF_NULL(user); + // dest string must be valid is accept + TEGO_THROW_IF_TRUE(response == tego_file_transfer_response_accept && + (destPath == nullptr || destPathLength == 0)) + // dest string must be null and empty if reject + TEGO_THROW_IF_TRUE(response == tego_file_transfer_response_reject && + (destPath != nullptr || destPathLength > 0)) + + context->respond_file_transfer_request( + user, + fileTransfer, + response, + destPath ? std::string(destPath, destPathLength) : std::string()); + }, error); + } + + void tego_context_cancel_file_transfer( + tego_context* context, + tego_user_id_t const* user, + tego_file_transfer_id_t id, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(context); + TEGO_THROW_IF_FALSE(context->threadId == std::this_thread::get_id()); + TEGO_THROW_IF_NULL(user); + context->cancel_file_transfer_transfer(user, id); + }, error); + } + + void tego_context_send_message( + tego_context_t* context, + const tego_user_id_t* user, + const char* message, + size_t messageLength, + tego_message_id_t* out_id, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(context); + TEGO_THROW_IF_FALSE(context->threadId == std::this_thread::get_id()); + TEGO_THROW_IF_NULL(user); + TEGO_THROW_IF_NULL(message); + TEGO_THROW_IF_FALSE(messageLength > 0); + + auto id = context->send_message(user, std::string(message, messageLength)); + if (out_id != nullptr) + { + logger::println("Sent message with id: {}", id); + *out_id = id; + } + }, error); + } + + void tego_context_forget_user( + tego_context_t* context, + const tego_user_id_t* user, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(context); + TEGO_THROW_IF_FALSE(context->threadId == std::this_thread::get_id()); + context->forget_user(user); + }, error); + } +} diff --git a/src/libtego/source/context.hpp b/src/libtego/source/context.hpp new file mode 100644 index 00000000..1c6e34d0 --- /dev/null +++ b/src/libtego/source/context.hpp @@ -0,0 +1,90 @@ +#pragma once + +#include "signals.hpp" +#include "tor.hpp" +#include "user.hpp" + +#include "tor/TorControl.h" +#include "tor/TorManager.h" +#include "core/IdentityManager.h" + +// +// Tego Context +// + +struct tego_context +{ +public: + tego_context(); + + void start_tor(const tego_tor_launch_config_t* config); + bool get_tor_daemon_configured() const; + size_t get_tor_logs_size() const; + const std::vector& get_tor_logs() const; + const char* get_tor_version_string() const; + tego_tor_control_status_t get_tor_control_status() const; + tego_tor_process_status_t get_tor_process_status() const; + tego_tor_network_status_t get_tor_network_status() const; + int32_t get_tor_bootstrap_progress() const; + tego_tor_bootstrap_tag_t get_tor_bootstrap_tag() const; + void start_service( + tego_ed25519_private_key_t const* hostPrivateKey, + tego_user_id_t const* const* userBuffer, + tego_user_type_t* const userTypeBuffer, + size_t userCount); + void start_service(); + void update_tor_daemon_config(const tego_tor_daemon_config_t* config); + void save_tor_daemon_config(); + void set_host_user_state(tego_host_user_state_t state); + std::unique_ptr get_host_user_id() const; + tego_host_user_state_t get_host_user_state() const; + void send_chat_request( + const tego_user_id_t* user, + const char* message, + size_t messageLength); + void acknowledge_chat_request( + const tego_user_id_t* user, + tego_chat_acknowledge_t response); + tego_message_id_t send_message( + const tego_user_id_t* user, + const std::string& message); + tego_user_type_t get_user_type(tego_user_id_t const* user) const; + size_t get_user_count() const; + std::vector get_users() const; + void forget_user(const tego_user_id_t* user); + std::tuple, tego_file_size_t> send_file_transfer_request( + tego_user_id_t const* user, + std::string const& filePath); + void respond_file_transfer_request( + tego_user_id_t const* user, + tego_file_transfer_id_t fileTransfer, + tego_file_transfer_response_t response, + std::string const& destPath); + void cancel_file_transfer_transfer( + tego_user_id_t const* user, + tego_file_transfer_id_t); + + tego::callback_registry callback_registry_; + tego::callback_queue callback_queue_; + // anything that touches internal state should do so through + // this 'global' (actually per tego_context) mutex + std::mutex mutex_; + + // TODO: figure out ownership of these Qt types + Tor::TorManager* torManager = nullptr; + Tor::TorControl* torControl = nullptr; + IdentityManager* identityManager = nullptr; + + // we store the thread id that this context is associated with + // calls which go into our qt internals must be called from the same + // thread as the context was created on + // (this is not entirely true, they must be called from the thread with the Qt + // event loop, which in our case is the thread the context is created on) + std::thread::id threadId; +private: + class ContactUser* getContactUser(const tego_user_id_t*) const; + + mutable std::string torVersion; + mutable std::vector torLogs; + tego_host_user_state_t hostUserState = tego_host_user_state_unknown; +}; \ No newline at end of file diff --git a/src/core/ContactIDValidator.cpp b/src/libtego/source/core/ContactIDValidator.cpp similarity index 84% rename from src/core/ContactIDValidator.cpp rename to src/libtego/source/core/ContactIDValidator.cpp index 0d09d1d3..57eb83a6 100644 --- a/src/core/ContactIDValidator.cpp +++ b/src/libtego/source/core/ContactIDValidator.cpp @@ -30,12 +30,17 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include "IdentityManager.h" #include "ContactIDValidator.h" +#include "utils/StringUtil.h" -static QRegularExpression regex(QStringLiteral("(torsion|ricochet):([a-z2-7]{16})")); +// multiple consumers of this regex object seems to cause thread contention issues +// and segfaults, so make it thread_local to sidestep the issue for now +static thread_local QRegularExpression regex(QStringLiteral("ricochet:([a-z2-7]{56})")); ContactIDValidator::ContactIDValidator(QObject *parent) - : QRegularExpressionValidator(parent), m_uniqueIdentity(0) + : QRegularExpressionValidator(parent) + , m_uniqueIdentity(identityManager->identities()[0]) { setRegularExpression(regex); } @@ -91,19 +96,16 @@ QString ContactIDValidator::hostnameFromID(const QString &ID) if (!match.hasMatch()) return QString(); - return match.captured(2) + QStringLiteral(".onion"); + return match.captured(1) + QStringLiteral(".onion"); } QString ContactIDValidator::idFromHostname(const QString &hostname) { - QString re = hostname; - - if (re.size() != 16) - { - if (re.size() == 22 && re.toLower().endsWith(QLatin1String(".onion"))) - re.chop(6); - else - return QString(); + #define DOT_ONION ".onion" + + QString re = hostname.toLower(); + if (re.endsWith(DOT_ONION)) { + re.chop(static_strlen(DOT_ONION)); } re.prepend(QStringLiteral("ricochet:")); diff --git a/src/core/ContactIDValidator.h b/src/libtego/source/core/ContactIDValidator.h similarity index 84% rename from src/core/ContactIDValidator.h rename to src/libtego/source/core/ContactIDValidator.h index f75e8a45..363ba236 100644 --- a/src/core/ContactIDValidator.h +++ b/src/libtego/source/core/ContactIDValidator.h @@ -33,7 +33,6 @@ #ifndef CONTACTIDVALIDATOR_H #define CONTACTIDVALIDATOR_H -#include #include "UserIdentity.h" class ContactIDValidator : public QRegularExpressionValidator @@ -41,8 +40,6 @@ class ContactIDValidator : public QRegularExpressionValidator Q_OBJECT Q_DISABLE_COPY(ContactIDValidator) - Q_PROPERTY(UserIdentity* notContactOfIdentity READ notContactOfIdentity WRITE setNotContactOfIdentity) - public: ContactIDValidator(QObject *parent = 0); @@ -51,14 +48,11 @@ class ContactIDValidator : public QRegularExpressionValidator static QString idFromHostname(const QString &hostname); static QString idFromHostname(const QByteArray &hostname) { return idFromHostname(QString::fromLatin1(hostname)); } - UserIdentity *notContactOfIdentity() const { return m_uniqueIdentity; } - void setNotContactOfIdentity(UserIdentity *i) { m_uniqueIdentity = i; } - virtual void fixup(QString &text) const; virtual State validate(QString &text, int &pos) const; - Q_INVOKABLE ContactUser *matchingContact(const QString &text) const; - Q_INVOKABLE bool matchesIdentity(const QString &text) const; + ContactUser *matchingContact(const QString &text) const; + bool matchesIdentity(const QString &text) const; signals: void failed() const; diff --git a/src/core/ContactUser.cpp b/src/libtego/source/core/ContactUser.cpp similarity index 64% rename from src/core/ContactUser.cpp rename to src/libtego/source/core/ContactUser.cpp index 3bc7505b..a5cc6b7e 100644 --- a/src/core/ContactUser.cpp +++ b/src/libtego/source/core/ContactUser.cpp @@ -40,100 +40,97 @@ #include "core/ConversationModel.h" #include "tor/HiddenService.h" #include "protocol/OutboundConnector.h" -#include -#include -#include -#include -ContactUser::ContactUser(UserIdentity *ident, int id, QObject *parent) +#include "ed25519.hpp" +#include "context.hpp" +#include "user.hpp" +#include "globals.hpp" + +ContactUser::ContactUser(UserIdentity *ident, const QString& hostname, Status status, QObject *parent) : QObject(parent) , identity(ident) - , uniqueID(id) , m_connection(0) , m_outgoingSocket(0) - , m_status(Offline) + , m_status(status) , m_lastReceivedChatID(0) , m_contactRequest(0) - , m_settings(0) , m_conversation(0) + , m_hostname(hostname) { - Q_ASSERT(uniqueID >= 0); + Q_ASSERT(hostname.endsWith(".onion")); - m_settings = new SettingsObject(QStringLiteral("contacts.%1").arg(uniqueID)); - connect(m_settings, &SettingsObject::modified, this, &ContactUser::onSettingsModified); + const auto serviceId = hostname.chopped(tego::static_strlen(".onion")); m_conversation = new ConversationModel(this); m_conversation->setContact(this); - loadContactRequest(); updateStatus(); updateOutgoingSocket(); } -ContactUser::~ContactUser() -{ - delete m_settings; -} - -void ContactUser::loadContactRequest() -{ - if (m_contactRequest) - return; - - if (m_settings->read("request.status") != QJsonValue::Undefined) { - m_contactRequest = new OutgoingContactRequest(this); - connect(m_contactRequest, &OutgoingContactRequest::statusChanged, this, &ContactUser::updateStatus); - connect(m_contactRequest, &OutgoingContactRequest::removed, this, &ContactUser::requestRemoved); - connect(m_contactRequest, &OutgoingContactRequest::accepted, this, &ContactUser::requestAccepted); - updateStatus(); - } -} - -ContactUser *ContactUser::addNewContact(UserIdentity *identity, int id) +void ContactUser::createContactRequest(const QString& msg) { - ContactUser *user = new ContactUser(identity, id); - user->settings()->write("whenCreated", QDateTime::currentDateTime()); + m_contactRequest = new OutgoingContactRequest(this, msg); - return user; + connect(m_contactRequest, &OutgoingContactRequest::statusChanged, this, &ContactUser::updateStatus); + connect(m_contactRequest, &OutgoingContactRequest::removed, this, &ContactUser::requestRemoved); + connect(m_contactRequest, &OutgoingContactRequest::accepted, this, &ContactUser::requestAccepted); + updateStatus(); } void ContactUser::updateStatus() { + logger::trace(); Status newStatus; if (m_contactRequest) { if (m_contactRequest->status() == OutgoingContactRequest::Error || m_contactRequest->status() == OutgoingContactRequest::Rejected) { newStatus = RequestRejected; + logger::trace(); } else { newStatus = RequestPending; + logger::trace(); } } else if (m_connection && m_connection->isConnected()) { newStatus = Online; - } else if (settings()->read("rejected").toBool()) { + logger::trace(); + } else if (m_status == RequestRejected) { newStatus = RequestRejected; - } else if (settings()->read("sentUpgradeNotification").toBool()) { - newStatus = Outdated; } else { newStatus = Offline; + logger::trace(); } if (newStatus == m_status) + { + logger::trace(); return; + } + + { + auto userId = this->toTegoUserId(); + switch(newStatus) + { + case ContactUser::Online: + tego::g_globals.context->callback_registry_.emit_user_status_changed(userId.release(), tego_user_status_online); + break; + case ContactUser::Offline: + tego::g_globals.context->callback_registry_.emit_user_status_changed(userId.release(), tego_user_status_offline); + break; + default: + + // noop, other statuses are handled elsewhere + break; + } + } m_status = newStatus; emit statusChanged(); updateOutgoingSocket(); } -void ContactUser::onSettingsModified(const QString &key, const QJsonValue &value) -{ - Q_UNUSED(value); - if (key == QLatin1String("nickname")) - emit nicknameChanged(); -} - void ContactUser::updateOutgoingSocket() { if (m_status != Offline && m_status != RequestPending) { @@ -151,7 +148,7 @@ void ContactUser::updateOutgoingSocket() return; if (m_outgoingSocket && m_outgoingSocket->status() == Protocol::OutboundConnector::Ready) { - BUG() << "Called updateOutgoingSocket with an existing socket in Ready. This should've been deleted."; + TEGO_BUG() << "Called updateOutgoingSocket with an existing socket in Ready. This should've been deleted."; m_outgoingSocket->disconnect(this); m_outgoingSocket->deleteLater(); m_outgoingSocket = 0; @@ -165,50 +162,6 @@ void ContactUser::updateOutgoingSocket() assignConnection(m_outgoingSocket->takeConnection()); } ); - - /* As an ugly hack, because Ricochet 1.0.x versions have no way to notify about - * protocol issues, and it's not feasible to support both protocols for this - * tiny upgrade period: - * - * The first time we make an outgoing connection to an existing contact, if they - * are using the old version, send a chat message that lets them know about the - * new version, then disconnect. This message is only sent once per contact. - * - * XXX: This logic should be removed an appropriate amount of time after the new - * protocol has been released. - */ - connect(m_outgoingSocket, &Protocol::OutboundConnector::oldVersionNegotiated, this, - [this](QTcpSocket *socket) { - if (m_settings->read("sentUpgradeNotification").toBool()) - return; - QByteArray secret = m_settings->read("remoteSecret"); - if (secret.size() != 16) - return; - - static const char upgradeMessage[] = - "[automatic message] I'm using a newer version of Ricochet that is not " - "compatible with yours. This is a one-time change to help improve Ricochet. " - "See https://ricochet.im/upgrade for instructions on getting the latest " - "version. Once you have upgraded, I will be able to see your messages again."; - uchar command[] = { - 0x00, 0x00, 0x10, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - - qToBigEndian(quint16(sizeof(upgradeMessage) + 7), command); - qToBigEndian(quint16(sizeof(upgradeMessage) - 1), command + sizeof(command) - sizeof(quint16)); - - QByteArray data; - data.append((char)0x00); - data.append(secret); - data.append(reinterpret_cast(command), sizeof(command)); - data.append(upgradeMessage); - socket->write(data); - - m_settings->write("sentUpgradeNotification", true); - updateStatus(); - } - ); } m_outgoingSocket->connectToHost(hostname(), port()); @@ -223,26 +176,11 @@ void ContactUser::onConnected() return; } - m_settings->write("lastConnected", QDateTime::currentDateTime()); - if (m_contactRequest && m_connection->purpose() == Protocol::Connection::Purpose::OutboundRequest) { - qDebug() << "Sending contact request for" << uniqueID << nickname(); + qDebug() << "Sending contact request for " << m_hostname; m_contactRequest->sendRequest(m_connection); } - if (!m_settings->read("sentUpgradeNotification").isNull()) - m_settings->unset("sentUpgradeNotification"); - - /* The 'rejected' mark comes from failed authentication to someone who we thought was a known - * contact. Normally, it would mean that you were removed from that person's contacts. It's - * possible for this to be undone; for example, if that person sends you a new contact request, - * it will be automatically accepted. If this happens, unset the 'rejected' flag for correct UI. - */ - if (m_settings->read("rejected").toBool()) { - qDebug() << "Contact had marked us as rejected, but now they've connected again. Re-enabling."; - m_settings->unset("rejected"); - } - updateStatus(); if (isConnected()) { emit connected(); @@ -250,25 +188,24 @@ void ContactUser::onConnected() } if (m_status != Online && m_status != RequestPending) { - BUG() << "Contact has a connection while in status" << m_status << "which is not expected."; + TEGO_BUG() << "Contact has a connection while in status" << m_status << "which is not expected."; m_connection->close(); } } void ContactUser::onDisconnected() { - qDebug() << "Contact" << uniqueID << "disconnected"; - m_settings->write("lastConnected", QDateTime::currentDateTime()); + qDebug() << "Contact" << m_hostname << "disconnected"; if (m_connection) { if (m_connection->isConnected()) { - BUG() << "onDisconnected called, but connection is still connected"; + TEGO_BUG() << "onDisconnected called, but connection is still connected"; return; } m_connection.clear(); } else { - BUG() << "onDisconnected called without a connection"; + TEGO_BUG() << "onDisconnected called without a connection"; } updateStatus(); @@ -276,29 +213,14 @@ void ContactUser::onDisconnected() emit connectionChanged(m_connection); } -SettingsObject *ContactUser::settings() -{ - return m_settings; -} - -QString ContactUser::nickname() const -{ - return m_settings->read("nickname").toString(); -} - -void ContactUser::setNickname(const QString &nickname) -{ - m_settings->write("nickname", nickname); -} - QString ContactUser::hostname() const { - return m_settings->read("hostname").toString(); + return m_hostname; } quint16 ContactUser::port() const { - return m_settings->read("port", 9878).toInt(); + return 9878; } QString ContactUser::contactID() const @@ -313,7 +235,8 @@ void ContactUser::setHostname(const QString &hostname) if (!hostname.endsWith(QLatin1String(".onion"))) fh.append(QLatin1String(".onion")); - m_settings->write("hostname", fh); + m_hostname = hostname; + updateOutgoingSocket(); } @@ -322,7 +245,7 @@ void ContactUser::deleteContact() /* Anything that uses ContactUser is required to either respond to the contactDeleted signal * synchronously, or make use of QWeakPointer. */ - qDebug() << "Deleting contact" << uniqueID; + qDebug() << "Deleting contact" << m_hostname; if (m_contactRequest) { qDebug() << "Cancelling request associated with contact to be deleted"; @@ -332,14 +255,13 @@ void ContactUser::deleteContact() emit contactDeleted(this); - m_settings->undefine(); deleteLater(); } void ContactUser::requestAccepted() { if (!m_contactRequest) { - BUG() << "Request accepted but ContactUser doesn't know an active request"; + TEGO_BUG() << "Request accepted but ContactUser doesn't know an active request"; return; } @@ -363,12 +285,12 @@ void ContactUser::requestRemoved() void ContactUser::assignConnection(const QSharedPointer &connection) { if (connection == m_connection) { - BUG() << "Connection is already assigned to this ContactUser"; + TEGO_BUG() << "Connection is already assigned to this ContactUser"; return; } if (connection->purpose() == Protocol::Connection::Purpose::KnownContact) { - BUG() << "Connection is already assigned to a contact"; + TEGO_BUG() << "Connection is already assigned to a contact"; connection->close(); return; } @@ -376,13 +298,13 @@ void ContactUser::assignConnection(const QSharedPointer &c bool isOutbound = connection->direction() == Protocol::Connection::ClientSide; if (!connection->isConnected()) { - BUG() << "Connection assigned to contact but isn't connected; discarding"; + TEGO_BUG() << "Connection assigned to contact but isn't connected; discarding"; connection->close(); return; } if (!connection->hasAuthenticatedAs(Protocol::Connection::HiddenServiceAuth, hostname())) { - BUG() << "Connection assigned to contact without matching authentication"; + TEGO_BUG() << "Connection assigned to contact without matching authentication"; connection->close(); return; } @@ -400,10 +322,9 @@ void ContactUser::assignConnection(const QSharedPointer &c if (m_contactRequest && knownToPeer) { m_contactRequest->accept(); if (m_contactRequest) - BUG() << "Outgoing contact request not unset after implicit accept during connection"; + TEGO_BUG() << "Outgoing contact request not unset after implicit accept during connection"; } else if (!m_contactRequest && !knownToPeer) { qDebug() << "Contact says we're unknown; marking as rejected"; - settings()->write("rejected", true); connection->close(); updateStatus(); updateOutgoingSocket(); @@ -467,12 +388,12 @@ void ContactUser::assignConnection(const QSharedPointer &c } if (m_connection) { - BUG() << "After resolving connection races, ContactUser still has two connections"; + TEGO_BUG() << "After resolving connection races, ContactUser still has two connections"; connection->close(); return; } - qDebug() << "Assigned" << (isOutbound ? "outbound" : "inbound") << "connection to contact" << uniqueID; + qDebug() << "Assigned" << (isOutbound ? "outbound" : "inbound") << "connection to contact" << m_hostname; if (m_contactRequest && isOutbound) { if (!connection->setPurpose(Protocol::Connection::Purpose::OutboundRequest)) { @@ -482,7 +403,7 @@ void ContactUser::assignConnection(const QSharedPointer &c } } else { if (m_contactRequest && !isOutbound) { - qDebug() << "Implicitly accepting outgoing contact request for" << uniqueID << "due to incoming connection"; + qDebug() << "Implicitly accepting outgoing contact request for" << m_hostname << "due to incoming connection"; m_contactRequest->accept(); } @@ -505,7 +426,7 @@ void ContactUser::assignConnection(const QSharedPointer &c * kicks in. In particular, this is important to allow AuthHiddenServiceChannel to * respond before other channels are created. */ if (!metaObject()->invokeMethod(this, "onConnected", Qt::QueuedConnection)) - BUG() << "Failed queuing invocation of onConnected method"; + TEGO_BUG() << "Failed queuing invocation of onConnected method"; } void ContactUser::clearConnection() @@ -518,3 +439,14 @@ void ContactUser::clearConnection() m_connection.clear(); } +std::unique_ptr ContactUser::toTegoUserId() const +{ + // convert our hostname to just the service id raw string + auto serviceIdString = this->hostname().chopped(tego::static_strlen(".onion")).toUtf8(); + // ensure valid service id + auto serviceId = std::make_unique(serviceIdString.data(), serviceIdString.size()); + // create user id object from service id + auto userId = std::make_unique(*serviceId.get()); + + return userId; +} \ No newline at end of file diff --git a/src/core/ContactUser.h b/src/libtego/source/core/ContactUser.h similarity index 78% rename from src/core/ContactUser.h rename to src/libtego/source/core/ContactUser.h index 40ee9bef..8943cf0b 100644 --- a/src/core/ContactUser.h +++ b/src/libtego/source/core/ContactUser.h @@ -33,12 +33,6 @@ #ifndef CONTACTUSER_H #define CONTACTUSER_H -#include -#include -#include -#include -#include -#include "utils/Settings.h" #include "protocol/Connection.h" class UserIdentity; @@ -59,16 +53,6 @@ class ContactUser : public QObject { Q_OBJECT Q_DISABLE_COPY(ContactUser) - Q_ENUMS(Status) - - Q_PROPERTY(int uniqueID READ getUniqueID CONSTANT) - Q_PROPERTY(UserIdentity* identity READ getIdentity CONSTANT) - Q_PROPERTY(QString nickname READ nickname WRITE setNickname NOTIFY nicknameChanged) - Q_PROPERTY(QString contactID READ contactID CONSTANT) - Q_PROPERTY(Status status READ status NOTIFY statusChanged) - Q_PROPERTY(OutgoingContactRequest *contactRequest READ contactRequest NOTIFY statusChanged) - Q_PROPERTY(SettingsObject *settings READ settings CONSTANT) - Q_PROPERTY(ConversationModel *conversation READ conversation CONSTANT) friend class ContactsManager; friend class OutgoingContactRequest; @@ -80,14 +64,12 @@ class ContactUser : public QObject Offline, RequestPending, RequestRejected, - Outdated + Outdated // this doesn't seem to actually be used }; UserIdentity * const identity; - const int uniqueID; - explicit ContactUser(UserIdentity *identity, int uniqueID, QObject *parent = 0); - virtual ~ContactUser(); + explicit ContactUser(UserIdentity *identity, const QString& hostname, Status status=Offline, QObject *parent = 0); const QSharedPointer &connection() { return m_connection; } bool isConnected() const { return status() == Online; } @@ -96,9 +78,7 @@ class ContactUser : public QObject ConversationModel *conversation() { return m_conversation; } UserIdentity *getIdentity() const { return identity; } - int getUniqueID() const { return uniqueID; } - QString nickname() const; /* Hostname is in the onion hostname format, i.e. it ends with .onion */ QString hostname() const; quint16 port() const; @@ -107,9 +87,9 @@ class ContactUser : public QObject Status status() const { return m_status; } - SettingsObject *settings(); + void deleteContact(); - Q_INVOKABLE void deleteContact(); + std::unique_ptr toTegoUserId() const; public slots: /* Assign a connection to this user @@ -128,7 +108,6 @@ public slots: */ void assignConnection(const QSharedPointer &connection); - void setNickname(const QString &nickname); void setHostname(const QString &hostname); void updateStatus(); @@ -147,7 +126,6 @@ private slots: void onDisconnected(); void requestRemoved(); void requestAccepted(); - void onSettingsModified(const QString &key, const QJsonValue &value); private: QSharedPointer m_connection; @@ -156,13 +134,13 @@ private slots: Status m_status; quint16 m_lastReceivedChatID; OutgoingContactRequest *m_contactRequest; - SettingsObject *m_settings; ConversationModel *m_conversation; + mutable QString m_hostname; /* See ContactsManager::addContact */ - static ContactUser *addNewContact(UserIdentity *identity, int id); + static ContactUser *addNewContact(UserIdentity *identity, const QString& contactHostname); - void loadContactRequest(); + void createContactRequest(const QString& msg); void updateOutgoingSocket(); void clearConnection(); diff --git a/src/core/ContactsManager.cpp b/src/libtego/source/core/ContactsManager.cpp similarity index 65% rename from src/core/ContactsManager.cpp rename to src/libtego/source/core/ContactsManager.cpp index 05ffd76e..9b208495 100644 --- a/src/core/ContactsManager.cpp +++ b/src/libtego/source/core/ContactsManager.cpp @@ -35,55 +35,74 @@ #include "OutgoingContactRequest.h" #include "ContactIDValidator.h" #include "ConversationModel.h" -#include -#include - -#ifdef Q_OS_MAC -#include -#endif +#include "protocol/ChatChannel.h" ContactsManager *contactsManager = 0; ContactsManager::ContactsManager(UserIdentity *id) - : identity(id), incomingRequests(this), highestID(-1) + : identity(id), incomingRequests(this) { contactsManager = this; } -void ContactsManager::loadFromSettings() + +// tego_user_type_allowed +void ContactsManager::addAllowedContacts(const QList& userHostnames) { - SettingsObject settings(QStringLiteral("contacts")); - foreach (const QString &key, settings.data().keys()) + for(const auto& hostname : userHostnames) { - bool ok = false; - int id = key.toInt(&ok); - if (!ok) - { - qWarning() << "Ignoring contact" << key << " with a non-integer ID"; - continue; - } - - ContactUser *user = new ContactUser(identity, id, this); + ContactUser *user = new ContactUser(identity, hostname, ContactUser::Offline, this); connectSignals(user); pContacts.append(user); emit contactAdded(user); - highestID = qMax(id, highestID); } +} - incomingRequests.loadRequests(); +// tego_user_type_requesting +void ContactsManager::addIncomingRequests(const QList& userHostnames) +{ + this->incomingRequests.loadRequests(userHostnames); +} + +// tego_user_type_blocked +void ContactsManager::addRejectedIncomingRequests(const QList& userHostnames) +{ + for(const auto& hostname : userHostnames) + { + this->incomingRequests.addRejectedHost(hostname.toUtf8()); + } +} + +// tego_user_type_pending +void ContactsManager::addOutgoingRequests(const QList& userHostnames) +{ + for(const auto& hostname : userHostnames) + { + this->createContactRequest(QString("ricochet:%1").arg(hostname), QString()); + } } -ContactUser *ContactsManager::addContact(const QString &nickname) +// tego_user_type_rejected +void ContactsManager::addRejectedOutgoingRequests(const QList& userHostnames) { - Q_ASSERT(!nickname.isEmpty()); + for(const auto& hostname : userHostnames) + { + ContactUser *user = new ContactUser(identity, hostname, ContactUser::RequestRejected, this); + + connect(user, SIGNAL(contactDeleted(ContactUser*)), SLOT(contactDeleted(ContactUser*))); + pContacts.append(user); + + // emit contactAdded(user); + } +} - highestID++; - ContactUser *user = ContactUser::addNewContact(identity, highestID); +ContactUser *ContactsManager::addContact(const QString& hostname) +{ + ContactUser *user = new ContactUser(identity, hostname); user->setParent(this); - user->setNickname(nickname); connectSignals(user); - qDebug() << "Added new contact" << nickname << "with ID" << user->uniqueID; + qDebug() << "Added new contact" << hostname; pContacts.append(user); emit contactAdded(user); @@ -98,23 +117,26 @@ void ContactsManager::connectSignals(ContactUser *user) connect(user, &ContactUser::statusChanged, [this,user]() { emit contactStatusChanged(user, user->status()); }); } -ContactUser *ContactsManager::createContactRequest(const QString &contactid, const QString &nickname, - const QString &myNickname, const QString &message) +ContactUser *ContactsManager::createContactRequest(const QString &contactid, const QString &message) { + logger::println("contactId : {}", contactid); + logger::println("message : {}", message); + QString hostname = ContactIDValidator::hostnameFromID(contactid); - if (hostname.isEmpty() || lookupHostname(contactid) || lookupNickname(nickname)) + if (hostname.isEmpty() || lookupHostname(contactid)) { return 0; } bool b = blockSignals(true); - ContactUser *user = addContact(nickname); + const auto contactHostname = ContactIDValidator::hostnameFromID(contactid); + ContactUser *user = addContact(contactHostname); blockSignals(b); if (!user) return user; user->setHostname(ContactIDValidator::hostnameFromID(contactid)); - OutgoingContactRequest::createNewRequest(user, myNickname, message); + OutgoingContactRequest::createNewRequest(user, message); /* Signal deferred from addContact to avoid changing the status immediately */ Q_ASSERT(user->status() == ContactUser::RequestPending); @@ -127,19 +149,6 @@ void ContactsManager::contactDeleted(ContactUser *user) pContacts.removeOne(user); } -ContactUser *ContactsManager::lookupSecret(const QByteArray &secret) const -{ - Q_ASSERT(secret.size() == 16); - - for (QList::ConstIterator it = pContacts.begin(); it != pContacts.end(); ++it) - { - if (secret == (*it)->settings()->read("localSecret")) - return *it; - } - - return 0; -} - ContactUser *ContactsManager::lookupHostname(const QString &hostname) const { QString ohost = ContactIDValidator::hostnameFromID(hostname); @@ -158,28 +167,6 @@ ContactUser *ContactsManager::lookupHostname(const QString &hostname) const return 0; } -ContactUser *ContactsManager::lookupNickname(const QString &nickname) const -{ - for (QList::ConstIterator it = pContacts.begin(); it != pContacts.end(); ++it) - { - if (QString::compare(nickname, (*it)->nickname(), Qt::CaseInsensitive) == 0) - return *it; - } - - return 0; -} - -ContactUser *ContactsManager::lookupUniqueID(int uniqueID) const -{ - for (QList::ConstIterator it = pContacts.begin(); it != pContacts.end(); ++it) - { - if ((*it)->uniqueID == uniqueID) - return *it; - } - - return 0; -} - void ContactsManager::onUnreadCountChanged() { ConversationModel *model = qobject_cast(sender()); @@ -189,11 +176,6 @@ void ContactsManager::onUnreadCountChanged() ContactUser *user = model->contact(); emit unreadCountChanged(user, model->unreadCount()); - -#ifdef Q_OS_MAC - int unread = globalUnreadCount(); - QtMac::setBadgeLabelText(unread == 0 ? QString() : QString::number(unread)); -#endif } int ContactsManager::globalUnreadCount() const diff --git a/src/core/ContactsManager.h b/src/libtego/source/core/ContactsManager.h similarity index 81% rename from src/core/ContactsManager.h rename to src/libtego/source/core/ContactsManager.h index 1c062772..5b20f2f3 100644 --- a/src/core/ContactsManager.h +++ b/src/libtego/source/core/ContactsManager.h @@ -33,10 +33,7 @@ #ifndef CONTACTSMANAGER_H #define CONTACTSMANAGER_H -#include -#include -#include "ContactUser.h" -#include "IncomingRequestManager.h" +#include "core/IncomingRequestManager.h" class OutgoingContactRequest; class UserIdentity; @@ -47,9 +44,6 @@ class ContactsManager : public QObject Q_OBJECT Q_DISABLE_COPY(ContactsManager) - Q_PROPERTY(IncomingRequestManager* incomingRequests READ incomingRequestManager CONSTANT) - Q_PROPERTY(int globalUnreadCount READ globalUnreadCount NOTIFY unreadCountChanged) - friend class OutgoingContactRequest; public: @@ -63,20 +57,26 @@ class ContactsManager : public QObject const QList &contacts() const { return pContacts; } ContactUser *lookupSecret(const QByteArray &secret) const; ContactUser *lookupHostname(const QString &hostname) const; - ContactUser *lookupNickname(const QString &nickname) const; - ContactUser *lookupUniqueID(int uniqueID) const; /* Create a new user and a contact request for that user. Use this instead of addContact. * Note that contactID should be an ricochet: ID. */ - Q_INVOKABLE ContactUser *createContactRequest(const QString &contactID, const QString &nickname, - const QString &myNickname, const QString &message); + ContactUser *createContactRequest(const QString &contactID, const QString &message); /* addContact will add the contact, but does not create a request. Use createContactRequest */ - ContactUser *addContact(const QString &nickname); + ContactUser *addContact(const QString& hostname); static QString hostnameFromID(const QString &ID); - void loadFromSettings(); + // tego_user_type_allowed + void addAllowedContacts(const QList& userHostnames); + // tego_user_type_requesting + void addIncomingRequests(const QList& userHostnames); + // tego_user_type_blocked + void addRejectedIncomingRequests(const QList& userHostnames); + // tego_user_type_pending + void addOutgoingRequests(const QList& userHostnames); + // tego_user_type_rejected + void addRejectedOutgoingRequests(const QList& userHostnames); int globalUnreadCount() const; @@ -94,7 +94,6 @@ private slots: private: QList pContacts; - int highestID; void connectSignals(ContactUser *user); }; diff --git a/src/libtego/source/core/ConversationModel.cpp b/src/libtego/source/core/ConversationModel.cpp new file mode 100644 index 00000000..db981ef9 --- /dev/null +++ b/src/libtego/source/core/ConversationModel.cpp @@ -0,0 +1,579 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "error.hpp" +#include "file_hash.hpp" +#include "globals.hpp" +using tego::g_globals; + +#include "ConversationModel.h" +#include "protocol/Connection.h" +#include "protocol/ChatChannel.h" +#include "protocol/FileChannel.h" +#include "utils/SecureRNG.h" +#include "utils/Useful.h" + +ConversationModel::ConversationModel(QObject *parent) + : QAbstractListModel(parent) + , m_contact(0) + , messages({}) + , m_unreadCount(0) + , lastMessageId(SecureRNG::randomInt(UINT32_MAX)) + +{ +} + +void ConversationModel::setContact(ContactUser *contact) +{ + if (contact == m_contact) + return; + + beginResetModel(); + messages.clear(); + + if (m_contact) + disconnect(m_contact, 0, this, 0); + m_contact = contact; + if (m_contact) { + auto connectChannel = [this](Protocol::Channel *channel) { + if (channel->direction() == Protocol::Channel::Outbound) + { + connect(channel, &Protocol::Channel::invalidated, this, &ConversationModel::outboundChannelClosed); + sendQueuedMessages(); + } + + if (Protocol::ChatChannel *chat = qobject_cast(channel)) + { + connect(chat, &Protocol::ChatChannel::messageReceived, this, &ConversationModel::messageReceived); + connect(chat, &Protocol::ChatChannel::messageAcknowledged, this, &ConversationModel::messageAcknowledged); + } + else if (auto fc = qobject_cast(channel); fc != nullptr) + { + connect(fc, &Protocol::FileChannel::fileTransferRequestReceived, this, &ConversationModel::onFileTransferRequestReceived); + connect(fc, &Protocol::FileChannel::fileTransferAcknowledged, this, &ConversationModel::onFileTransferAcknowledged); + connect(fc, &Protocol::FileChannel::fileTransferRequestResponded, this, &ConversationModel::onFileTransferRequestResponded); + connect(fc, &Protocol::FileChannel::fileTransferProgress, this, &ConversationModel::onFileTransferProgress); + connect(fc, &Protocol::FileChannel::fileTransferFinished, this, &ConversationModel::onFileTransferFinished); + } + }; + + auto connectConnection = [this,connectChannel]() { + if (m_contact->connection()) { + connect(m_contact->connection().data(), &Protocol::Connection::channelOpened, this, connectChannel); + foreach (auto channel, m_contact->connection()->findChannels()) + connectChannel(channel); + sendQueuedMessages(); + } + }; + + connect(m_contact, &ContactUser::connected, this, connectConnection); + connectConnection(); + connect(m_contact, &ContactUser::statusChanged, + this, &ConversationModel::onContactStatusChanged); + } + + endResetModel(); + emit contactChanged(); +} + +/* Get a channel of type T for a contact, if it doesn't exist create one + * on error returns NULL */ +template T *findOrCreateChannelForContact(ContactUser *contact, Protocol::Channel::Direction direction) { + T *channel = contact->connection()->findChannel(direction); + if (!channel) { + /* create a new channel */ + channel = new T(direction, contact->connection().data()); + if (!channel->openChannel()) + { + delete channel; + channel = nullptr; + } + } + return channel; +} + + +std::tuple, tego_file_size_t> ConversationModel::sendFile(const QString &file_uri) +{ + logger::println("Sending file: {}", file_uri); + + MessageData message(File, file_uri, QDateTime::currentDateTime(), lastMessageId++, Queued); + message.type = ConversationModel::MessageType::File; + + std::unique_ptr fileHash; + + // calculate our file hash + if(std::ifstream file(file_uri.toStdString(), std::ios::in | std::ios::binary); file.is_open()) + { + fileHash = std::make_unique(file); + // copy for our message + message.fileHash = *fileHash; + } + else + { + TEGO_THROW_MSG("Could not open file {}", file_uri); + } + + // calculate file size + const tego_file_size_t fileSize = static_cast(QFileInfo(file_uri).size()); + + if (m_contact->connection()) + { + logger::trace(); + auto channel = findOrCreateChannelForContact(m_contact, Protocol::Channel::Outbound); + if (channel && channel->isOpened()) + { + logger::trace(); + if (channel->sendFileWithId(message.text, message.fileHash, QDateTime(), message.identifier)) + { + logger::trace(); + message.status = Sending; + } + else + { + logger::trace(); + message.status = Error; + } + message.attemptCount++; + } + } + else + { + logger::trace(); + } + + beginInsertRows(QModelIndex(), 0, 0); + messages.prepend(message); + endInsertRows(); + prune(); + + return {message.identifier, std::move(fileHash), fileSize}; +} + +tego_message_id_t ConversationModel::sendMessage(const QString &text) +{ + if (text.isEmpty()) + return 0; + + MessageData message(Message, text, QDateTime::currentDateTime(), lastMessageId++, Queued); + + if (m_contact->connection()) + { + auto channel = findOrCreateChannelForContact(m_contact, Protocol::Channel::Outbound); + if (channel && channel->isOpened()) + { + if (channel->sendChatMessageWithId(text, QDateTime(), message.identifier)) + { + message.status = Sending; + } + else + { + message.status = Error; + } + message.attemptCount++; + } + } + + beginInsertRows(QModelIndex(), 0, 0); + messages.prepend(message); + endInsertRows(); + prune(); + + return message.identifier; +} + +void ConversationModel::acceptFile(tego_file_transfer_id_t id, const std::string& dest) +{ + TEGO_THROW_IF_FALSE(m_contact->connection()); + auto channel = findOrCreateChannelForContact(m_contact, Protocol::Channel::Inbound); + TEGO_THROW_IF_NULL(channel); + TEGO_THROW_IF_FALSE(channel->isOpened()); + + channel->acceptFile(id, dest); +} + +void ConversationModel::rejectFile(tego_file_transfer_id_t id) +{ + TEGO_THROW_IF_FALSE(m_contact->connection()); + auto channel = findOrCreateChannelForContact(m_contact, Protocol::Channel::Inbound); + TEGO_THROW_IF_NULL(channel); + TEGO_THROW_IF_FALSE(channel->isOpened()); + + channel->rejectFile(id); +} + +void ConversationModel::cancelTransfer(tego_file_transfer_id_t id) +{ + if(m_contact->connection()) + { + // first try cancelling an inbound transfer + if (auto channel = findOrCreateChannelForContact(m_contact, Protocol::Channel::Inbound); + channel != nullptr) + { + if (channel->isOpened() && channel->cancelTransfer(id)) + { + return; + } + } + + // next try cancelling an outbound transfer + if (auto channel = findOrCreateChannelForContact(m_contact, Protocol::Channel::Outbound); + channel != nullptr) + { + if (channel->isOpened() && channel->cancelTransfer(id)) + { + return; + } + } + } + else if(auto it = std::find_if(messages.begin(), messages.end(), [=](auto& msg) {return msg.identifier == id;}); + it != messages.end()) + { + messages.erase(it); + } + else + { + TEGO_THROW_MSG("Tego transfer {} does not exist", id); + } +} + + +void ConversationModel::sendQueuedMessages() +{ + if (!m_contact->connection()) + return; + + auto chat_channel = findOrCreateChannelForContact(m_contact, Protocol::Channel::Outbound); + auto file_channel = findOrCreateChannelForContact(m_contact, Protocol::Channel::Outbound); + + // sendQueuedMessages is called at channelOpened + + // Iterate backwards, from oldest to newest messages + for (int i = messages.size() - 1; i >= 0; i--) + { + auto& m = messages[i]; + if (m.status == Queued) { + qDebug() << "Sending queued chat message"; + bool attempted = false; + switch (m.type) + { + case ConversationModel::MessageType::Message: + if (chat_channel->isOpened()) + { + m.status = chat_channel->sendChatMessageWithId(m.text, m.time, m.identifier) ? Sending : Error; + attempted = true; + } + break; + case ConversationModel::MessageType::File: + if (file_channel->isOpened()) + { + logger::println("Attempted to send queued file: {}", m.text); + m.status = file_channel->sendFileWithId(m.text, m.fileHash, m.time, m.identifier) ? Sending : Error; + attempted = true; + } + break; + default: + TEGO_BUG() << "Rejected invalid message type"; + break; + }; + + if (attempted) + { + m.attemptCount++; + emit dataChanged(index(i, 0), index(i, 0)); + } + } + } +} + +void ConversationModel::messageReceived(const QString &text, const QDateTime &time, MessageId id) +{ + // In rare cases an outgoing acknowledgement packet can be lost which + // causes the other party to resend the message. Discard the duplicate. + // We don't need to resend the old acknowledgement packet because + // it is identical to the one for the duplicate message. + for (int i = 0; i < messages.size() && i < 5; i++) { + if (messages[i].status == Delivered) { + break; + } + if (messages[i].identifier == id && messages[i].text == text) { + qDebug() << "duplicate incoming message" << id; + return; + } + } + + // To preserve conversation flow despite potentially high latency, incoming messages + // are positioned above the last unacknowledged messages to the peer. We assume that + // the peer hadn't seen any unacknowledged message when this message was sent. + int row = 0; + for (int i = 0; i < messages.size() && i < 5; i++) { + if (messages[i].status != Sending && messages[i].status != Queued) { + row = i; + break; + } + } + + beginInsertRows(QModelIndex(), row, row); + MessageData message(Message, text, time, id, Received); + messages.insert(row, message); + endInsertRows(); + prune(); + + m_unreadCount++; + emit unreadCountChanged(); + + { + // convert QString to raw utf8 + auto utf8Text = text.toUtf8(); + auto rawText = std::make_unique(static_cast(utf8Text.size()) + 1u); + std::copy(utf8Text.begin(), utf8Text.end(), rawText.get()); + + auto userId = this->m_contact->toTegoUserId(); + + logger::println("Received Message : {}", rawText.get()); + + g_globals.context->callback_registry_.emit_message_received(userId.release(), static_cast(time.toMSecsSinceEpoch()), id, rawText.release(), static_cast(utf8Text.size())); + } +} + +void ConversationModel::messageAcknowledged(MessageId id, bool accepted) +{ + int row = indexOfIdentifier(id, true); + if (row < 0) + return; + + MessageData &data = messages[row]; + data.status = accepted ? Delivered : Error; + emit dataChanged(index(row, 0), index(row, 0)); + + auto userId = this->contact()->toTegoUserId(); + g_globals.context->callback_registry_.emit_message_acknowledged(userId.release(), id, (accepted ? TEGO_TRUE : TEGO_FALSE)); +} + +void ConversationModel::outboundChannelClosed() +{ + // Any messages that are Sending are moved back to Queued, so they + // will be re-sent when we reconnect. + for (int i = 0; i < messages.size(); i++) { + if (messages[i].status != Sending) + continue; + if (messages[i].attemptCount >= 2) { + qDebug() << "Outbound chat channel closed, and unacknowledged message has been tried twice already. Marking as error."; + messages[i].status = Error; + } else { + qDebug() << "Outbound chat channel closed, putting unacknowledged chat message back in queue"; + messages[i].status = Queued; + } + emit dataChanged(index(i, 0), index(i, 0)); + } + + // Try to reopen the channel if we're still connected + if (m_contact && m_contact->connection() && m_contact->connection()->isConnected()) { + metaObject()->invokeMethod(this, "sendQueuedMessages", Qt::QueuedConnection); + } +} + +void ConversationModel::clear() +{ + if (messages.isEmpty()) + return; + + beginRemoveRows(QModelIndex(), 0, messages.size()-1); + messages.clear(); + endRemoveRows(); + + resetUnreadCount(); +} + +void ConversationModel::resetUnreadCount() +{ + if (m_unreadCount == 0) + return; + m_unreadCount = 0; + emit unreadCountChanged(); +} + +void ConversationModel::onContactStatusChanged() +{ + // Update in case section has changed + emit dataChanged(index(0, 0), index(rowCount()-1, 0), QVector() << SectionRole); +} + +void ConversationModel::onFileTransferRequestReceived(tego_file_transfer_id_t id, const QString& filename, tego_file_size_t fileSize, tego_file_hash_t hash) +{ + // user id + auto userId = this->contact()->toTegoUserId(); + + // filename + auto utf8Filename = filename.toUtf8(); + const auto rawFilenameLength = static_cast(utf8Filename.size()); + const auto rawFilenameSize = rawFilenameLength + 1; // for null terminator + auto rawFilename = std::make_unique(rawFilenameSize); + std::copy(utf8Filename.begin(), utf8Filename.end(), rawFilename.get()); + rawFilename[rawFilenameLength] = 0; + + // filehash + auto heapHash = std::make_unique(hash); + + g_globals.context->callback_registry_.emit_file_transfer_request_received( + userId.release(), + id, + rawFilename.release(), + rawFilenameLength, + fileSize, + heapHash.release()); +} + +void ConversationModel::onFileTransferAcknowledged(tego_file_transfer_id_t id, bool accepted) +{ + int row = indexOfIdentifier(id, true); + if (row < 0) + return; + + MessageData &data = messages[row]; + data.status = accepted ? Delivered : Error; + emit dataChanged(index(row, 0), index(row, 0)); + + auto userId = this->contact()->toTegoUserId(); + g_globals.context->callback_registry_.emit_file_transfer_request_acknowledged( + userId.release(), + id, + accepted ? TEGO_TRUE : TEGO_FALSE); +} + +void ConversationModel::onFileTransferRequestResponded(tego_file_transfer_id_t id, tego_file_transfer_response_t response) +{ + auto userId = this->contact()->toTegoUserId(); + g_globals.context->callback_registry_.emit_file_transfer_request_response_received( + userId.release(), + id, + response); +} + +void ConversationModel::onFileTransferProgress(tego_file_transfer_id_t id, tego_file_transfer_direction_t direction, uint64_t bytesTransmitted, uint64_t bytesTotal) +{ + auto userId = this->contact()->toTegoUserId(); + g_globals.context->callback_registry_.emit_file_transfer_progress( + userId.release(), + id, + direction, + bytesTransmitted, + bytesTotal); +} + +void ConversationModel::onFileTransferFinished(tego_file_transfer_id_t id, tego_file_transfer_direction_t direction, tego_file_transfer_result_t result) +{ + auto userId = this->contact()->toTegoUserId(); + g_globals.context->callback_registry_.emit_file_transfer_complete( + userId.release(), + id, + direction, + result); +} + +QHash ConversationModel::roleNames() const +{ + QHash roles; + roles[Qt::DisplayRole] = "text"; + roles[TimestampRole] = "timestamp"; + roles[IsOutgoingRole] = "isOutgoing"; + roles[StatusRole] = "status"; + roles[SectionRole] = "section"; + roles[TimespanRole] = "timespan"; + return roles; +} + +int ConversationModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + return messages.size(); +} + +QVariant ConversationModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= messages.size()) + return QVariant(); + + const MessageData &message = messages[index.row()]; + + switch (role) { + case Qt::DisplayRole: return message.text; + case TimestampRole: return message.time; + case IsOutgoingRole: return message.status != Received; + case StatusRole: return message.status; + + case SectionRole: { + if (m_contact->status() == ContactUser::Online) + return QString(); + if (index.row() < messages.size() - 1) { + const MessageData &next = messages[index.row()+1]; + if (next.status != Received && next.status != Delivered) + return QString(); + } + for (int i = 0; i <= index.row(); i++) { + if (messages[i].status == Received || messages[i].status == Delivered) + return QString(); + } + return QStringLiteral("offline"); + } + case TimespanRole: { + if (index.row() < messages.size() - 1) + return messages[index.row() + 1].time.secsTo(messages[index.row()].time); + else + return -1; + } + } + + return QVariant(); +} + +int ConversationModel::indexOfIdentifier(MessageId identifier, bool isOutgoing) const +{ + for (int i = 0; i < messages.size(); i++) { + if (messages[i].identifier == identifier && (messages[i].status != Received) == isOutgoing) + return i; + } + return -1; +} + +void ConversationModel::prune() +{ + const int history_limit = 1000; + if (messages.size() > history_limit) { + beginRemoveRows(QModelIndex(), history_limit, messages.size()-1); + while (messages.size() > history_limit) { + messages.removeLast(); + } + endRemoveRows(); + } +} diff --git a/src/core/ConversationModel.h b/src/libtego/source/core/ConversationModel.h similarity index 66% rename from src/core/ConversationModel.h rename to src/libtego/source/core/ConversationModel.h index 5c95909d..81529b4a 100644 --- a/src/core/ConversationModel.h +++ b/src/libtego/source/core/ConversationModel.h @@ -33,21 +33,16 @@ #ifndef CONVERSATIONMODEL_H #define CONVERSATIONMODEL_H -#include -#include #include "core/ContactUser.h" #include "protocol/ChatChannel.h" +#include "protocol/FileChannel.h" class ConversationModel : public QAbstractListModel { Q_OBJECT - Q_ENUMS(MessageStatus) - - Q_PROPERTY(ContactUser* contact READ contact WRITE setContact NOTIFY contactChanged) - Q_PROPERTY(int unreadCount READ unreadCount RESET resetUnreadCount NOTIFY unreadCountChanged) - public: typedef Protocol::ChatChannel::MessageId MessageId; + static_assert(std::is_same_v); enum { TimestampRole = Qt::UserRole, @@ -65,20 +60,30 @@ class ConversationModel : public QAbstractListModel Error }; + enum MessageType { + Message, + File + }; + ConversationModel(QObject *parent = 0); ContactUser *contact() const { return m_contact; } void setContact(ContactUser *contact); int unreadCount() const { return m_unreadCount; } - Q_INVOKABLE void resetUnreadCount(); + void resetUnreadCount(); virtual QHash roleNames() const; virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; -public slots: - void sendMessage(const QString &text); + std::tuple, tego_file_size_t> sendFile(const QString &file_url); + tego_message_id_t sendMessage(const QString &text); + + void acceptFile(tego_file_transfer_id_t id, const std::string& dest); + void rejectFile(tego_file_transfer_id_t id); + void cancelTransfer(tego_file_transfer_id_t id); + void clear(); signals: @@ -92,16 +97,24 @@ private slots: void sendQueuedMessages(); void onContactStatusChanged(); + void onFileTransferRequestReceived(tego_file_transfer_id_t id, const QString& filename, tego_file_size_t fileSize, tego_file_hash_t hash); + void onFileTransferAcknowledged(tego_file_transfer_id_t id, bool ack); + void onFileTransferRequestResponded(tego_file_transfer_id_t id, tego_file_transfer_response_t response); + void onFileTransferProgress(tego_file_transfer_id_t id, tego_file_transfer_direction_t direction, tego_file_size_t bytesTransmitted, tego_file_size_t bytesTotal); + void onFileTransferFinished(tego_file_transfer_id_t id, tego_file_transfer_direction_t direction, tego_file_transfer_result_t result); + private: struct MessageData { + MessageType type; QString text; + tego_file_hash_t fileHash; QDateTime time; MessageId identifier; MessageStatus status; quint8 attemptCount; - MessageData(const QString &text, const QDateTime &time, MessageId id, MessageStatus status) - : text(text), time(time), identifier(id), status(status), attemptCount(0) + MessageData(MessageType m_type, const QString &contents, const QDateTime &t, MessageId id, MessageStatus stat) + : type(m_type), text(contents), time(t), identifier(id), status(stat), attemptCount(0) { } }; @@ -110,6 +123,10 @@ private slots: QList messages; int m_unreadCount; + // The peer might use recent message IDs between connections to handle + // re-send. Start at a random ID to reduce chance of collisions, then increment + MessageId lastMessageId; + int indexOfIdentifier(MessageId identifier, bool isOutgoing) const; void prune(); }; diff --git a/src/core/IdentityManager.cpp b/src/libtego/source/core/IdentityManager.cpp similarity index 79% rename from src/core/IdentityManager.cpp rename to src/libtego/source/core/IdentityManager.cpp index 4e8fd23e..ee7b1f26 100644 --- a/src/core/IdentityManager.cpp +++ b/src/libtego/source/core/IdentityManager.cpp @@ -32,17 +32,24 @@ #include "IdentityManager.h" #include "ContactIDValidator.h" +#include "ContactUser.h" #include "core/OutgoingContactRequest.h" -#include IdentityManager *identityManager = 0; -IdentityManager::IdentityManager(QObject *parent) +IdentityManager::IdentityManager(const QString& serviceID, QObject *parent) : QObject(parent), highestID(-1) { identityManager = this; - loadFromSettings(); + if (serviceID.isEmpty()) + { + createIdentity(); + } + else + { + addIdentity(new UserIdentity(0, serviceID, this)); + } } IdentityManager::~IdentityManager() @@ -55,7 +62,6 @@ void IdentityManager::addIdentity(UserIdentity *identity) m_identities.append(identity); highestID = qMax(identity->uniqueID, highestID); - connect(&identity->contacts, SIGNAL(contactAdded(ContactUser*)), SLOT(onContactAdded(ContactUser*))); connect(&identity->contacts, SIGNAL(outgoingRequestAdded(OutgoingContactRequest*)), SLOT(onOutgoingRequest(OutgoingContactRequest*))); connect(&identity->contacts.incomingRequests, SIGNAL(requestAdded(IncomingContactRequest*)), @@ -66,29 +72,12 @@ void IdentityManager::addIdentity(UserIdentity *identity) emit identityAdded(identity); } -void IdentityManager::loadFromSettings() -{ - SettingsObject settings; - if (settings.read("identity") != QJsonValue::Undefined) - { - addIdentity(new UserIdentity(0, this)); - } - else - { - /* No identities exist (probably inital run); create one */ - createIdentity(); - } -} - -UserIdentity *IdentityManager::createIdentity(const QString &serviceDirectory, const QString &nickname) +UserIdentity *IdentityManager::createIdentity() { - UserIdentity *identity = UserIdentity::createIdentity(++highestID, serviceDirectory); + UserIdentity *identity = UserIdentity::createIdentity(++highestID); if (!identity) return identity; - if (!nickname.isEmpty()) - identity->setNickname(nickname); - addIdentity(identity); return identity; @@ -112,17 +101,6 @@ UserIdentity *IdentityManager::lookupHostname(const QString &hostname) const return 0; } -UserIdentity *IdentityManager::lookupNickname(const QString &nickname) const -{ - for (QList::ConstIterator it = m_identities.begin(); it != m_identities.end(); ++it) - { - if (QString::compare(nickname, (*it)->nickname(), Qt::CaseInsensitive) == 0) - return *it; - } - - return 0; -} - UserIdentity *IdentityManager::lookupUniqueID(int uniqueID) const { for (QList::ConstIterator it = m_identities.begin(); it != m_identities.end(); ++it) @@ -134,11 +112,6 @@ UserIdentity *IdentityManager::lookupUniqueID(int uniqueID) const return 0; } -void IdentityManager::onContactAdded(ContactUser *user) -{ - emit contactAdded(user, user->identity); -} - void IdentityManager::onOutgoingRequest(OutgoingContactRequest *request) { emit outgoingRequestAdded(request, request->user->identity); diff --git a/src/core/IdentityManager.h b/src/libtego/source/core/IdentityManager.h similarity index 59% rename from src/core/IdentityManager.h rename to src/libtego/source/core/IdentityManager.h index ccf9fdfb..e5c5b46f 100644 --- a/src/core/IdentityManager.h +++ b/src/libtego/source/core/IdentityManager.h @@ -33,47 +33,42 @@ #ifndef IDENTITYMANAGER_H #define IDENTITYMANAGER_H -#include -#include "UserIdentity.h" - +// TODO: this needs to go entirely, we do not have multiple simultaneous UserIdentity objects class IdentityManager : public QObject { Q_OBJECT Q_DISABLE_COPY(IdentityManager) public: - explicit IdentityManager(QObject *parent = 0); + // serviceID : string ED25519-V3 keyblob pulled from config.json, or empty string to create one + explicit IdentityManager(const QString& serviceID, QObject *parent = 0); ~IdentityManager(); - const QList &identities() const { return m_identities; } - UserIdentity *lookupNickname(const QString &nickname) const; - UserIdentity *lookupHostname(const QString &hostname) const; - UserIdentity *lookupUniqueID(int uniqueID) const; + const QList &identities() const { return m_identities; } + class UserIdentity *lookupHostname(const QString &hostname) const; + class UserIdentity *lookupUniqueID(int uniqueID) const; - UserIdentity *createIdentity(const QString &serviceDirectory = QString(), const QString &nickname = QString()); + class UserIdentity *createIdentity(); signals: - void identityAdded(UserIdentity *identity); - void contactAdded(ContactUser *user, UserIdentity *identity); - void contactDeleted(ContactUser *user, UserIdentity *identity); - void outgoingRequestAdded(OutgoingContactRequest *request, UserIdentity *identity); - void incomingRequestAdded(IncomingContactRequest *request, UserIdentity *identity); - void incomingRequestRemoved(IncomingContactRequest *request, UserIdentity *identity); + void identityAdded(class UserIdentity *identity); + void contactDeleted(class ContactUser *user, class UserIdentity *identity); + void outgoingRequestAdded(class OutgoingContactRequest *request, class UserIdentity *identity); + void incomingRequestAdded(class IncomingContactRequest *request, class UserIdentity *identity); + void incomingRequestRemoved(class IncomingContactRequest *request, class UserIdentity *identity); private slots: - void onContactAdded(ContactUser *user); - void onOutgoingRequest(OutgoingContactRequest *request); - void onIncomingRequest(IncomingContactRequest *request); - void onIncomingRequestRemoved(IncomingContactRequest *request); + void onOutgoingRequest(class OutgoingContactRequest *request); + void onIncomingRequest(class IncomingContactRequest *request); + void onIncomingRequestRemoved(class IncomingContactRequest *request); private: - QList m_identities; + QList m_identities; int highestID; - void loadFromSettings(); - void addIdentity(UserIdentity *identity); + void addIdentity(class UserIdentity *identity); }; -extern IdentityManager *identityManager; +extern class IdentityManager* identityManager; #endif // IDENTITYMANAGER_H diff --git a/src/core/IncomingRequestManager.cpp b/src/libtego/source/core/IncomingRequestManager.cpp similarity index 82% rename from src/core/IncomingRequestManager.cpp rename to src/libtego/source/core/IncomingRequestManager.cpp index 14432809..092bfb25 100644 --- a/src/core/IncomingRequestManager.cpp +++ b/src/libtego/source/core/IncomingRequestManager.cpp @@ -33,18 +33,47 @@ #include "IdentityManager.h" #include "IncomingRequestManager.h" #include "ContactsManager.h" +#include "ContactUser.h" #include "OutgoingContactRequest.h" #include "ContactIDValidator.h" #include "utils/Useful.h" #include "protocol/Connection.h" #include "protocol/ContactRequestChannel.h" +#include "ed25519.hpp" +#include "context.hpp" +#include "user.hpp" +#include "globals.hpp" + IncomingRequestManager::IncomingRequestManager(ContactsManager *c) : QObject(c), contacts(c) { connect(this, SIGNAL(requestAdded(IncomingContactRequest*)), this, SIGNAL(requestsChanged())); connect(this, SIGNAL(requestRemoved(IncomingContactRequest*)), this, SIGNAL(requestsChanged())); + + connect(this, &IncomingRequestManager::requestAdded, [self=this](IncomingContactRequest* request) -> void + { + // convert the hostname to user id + const auto hostname = request->hostname(); + auto serviceIdRaw = hostname.chopped(tego::static_strlen(".onion")); + + std::unique_ptr serviceId; + tego_v3_onion_service_id_from_string(tego::out(serviceId), serviceIdRaw.data(), static_cast(serviceIdRaw.size()), tego::throw_on_error()); + + std::unique_ptr userId; + tego_user_id_from_v3_onion_service_id(tego::out(userId), serviceId.get(), tego::throw_on_error()); + + auto message = request->message().toUtf8(); + + const auto messageLength = static_cast(message.size()); + auto rawMessage = std::make_unique(messageLength + 1); + std::copy(message.begin(), message.end(), rawMessage.get()); + + tego::g_globals.context->callback_registry_.emit_chat_request_received(userId.release(), rawMessage.release(), messageLength); + + logger::trace(); + }); auto attachChannel = [this](Protocol::Channel *channel) { if (Protocol::ContactRequestChannel *req = qobject_cast(channel)) { connect(req, &Protocol::ContactRequestChannel::requestReceived, this, &IncomingRequestManager::requestReceived); @@ -59,18 +88,11 @@ IncomingRequestManager::IncomingRequestManager(ContactsManager *c) ); } -void IncomingRequestManager::loadRequests() +void IncomingRequestManager::loadRequests(const QList userHostnames) { - SettingsObject settings(QStringLiteral("contactRequests")); - - foreach (const QString &hostStr, settings.data().keys()) { - QByteArray host = hostStr.toLatin1(); - if (!host.endsWith(".onion")) - host.append(".onion"); - - IncomingContactRequest *request = new IncomingContactRequest(this, host); - request->load(); - + for(const auto& hostname : userHostnames) + { + IncomingContactRequest* request = new IncomingContactRequest(this, hostname.toUtf8()); m_requests.append(request); emit requestAdded(request); } @@ -102,7 +124,7 @@ void IncomingRequestManager::requestReceived() { Protocol::ContactRequestChannel *channel = qobject_cast(sender()); if (!channel) { - BUG() << "Called without a valid sender"; + TEGO_BUG() << "Called without a valid sender"; return; } @@ -110,7 +132,7 @@ void IncomingRequestManager::requestReceived() QString hostname = channel->connection()->authenticatedIdentity(Protocol::Connection::HiddenServiceAuth); if (hostname.isEmpty() || !hostname.endsWith(QStringLiteral(".onion"))) { - BUG() << "Incoming contact request received but connection isn't authenticated"; + TEGO_BUG() << "Incoming contact request received but connection isn't authenticated"; channel->setResponseStatus(Response::Error); return; } @@ -144,7 +166,7 @@ void IncomingRequestManager::requestReceived() * contact, including an outgoing request. Those are implicitly accepted at * a different level. */ if (contacts->lookupHostname(hostname)) { - BUG() << "Created an inbound contact request matching a known contact; this shouldn't be allowed"; + TEGO_BUG() << "Created an inbound contact request matching a known contact; this shouldn't be allowed"; return; } @@ -168,18 +190,17 @@ void IncomingRequestManager::removeRequest(IncomingContactRequest *request) void IncomingRequestManager::addRejectedHost(const QByteArray &hostname) { - SettingsObject *settings = contacts->identity->settings(); - QJsonArray blacklist = settings->read("hostnameBlacklist"); - if (!blacklist.contains(QString::fromLatin1(hostname))) { - blacklist.append(QString::fromLatin1(hostname)); - settings->write("hostnameBlacklist", blacklist); - } + this->rejectedHosts.insert(hostname); } bool IncomingRequestManager::isHostnameRejected(const QByteArray &hostname) const { - QJsonArray blacklist = contacts->identity->settings()->read("hostnameBlacklist"); - return blacklist.contains(QString::fromLatin1(hostname)); + return this->rejectedHosts.contains(hostname); +} + +QList IncomingRequestManager::getRejectedHostnames() const +{ + return this->rejectedHosts.values(); } IncomingContactRequest::IncomingContactRequest(IncomingRequestManager *m, const QByteArray &h @@ -194,36 +215,14 @@ IncomingContactRequest::IncomingContactRequest(IncomingRequestManager *m, const qDebug() << "Created contact request from" << m_hostname << (connection ? "with" : "without") << "connection"; } -QString IncomingContactRequest::settingsKey() const -{ - QString key = QString(QLatin1String(m_hostname)); - key.chop(QStringLiteral(".onion").size()); - return QStringLiteral("contactRequests.%1").arg(key); -} - void IncomingContactRequest::load() { - SettingsObject settings(settingsKey()); - setNickname(settings.read("nickname").toString()); - setMessage(settings.read("message").toString()); - - m_requestDate = settings.read("requestDate"); - m_lastRequestDate = settings.read("lastRequestDate"); } void IncomingContactRequest::save() { - SettingsObject settings(settingsKey()); - - settings.write("nickname", nickname()); - settings.write("message", message()); - - if (m_requestDate.isNull()) - m_requestDate = m_lastRequestDate = QDateTime::currentDateTime(); - settings.write("requestDate", m_requestDate); - settings.write("lastRequestDate", m_lastRequestDate); } void IncomingContactRequest::renew() @@ -233,7 +232,7 @@ void IncomingContactRequest::renew() void IncomingContactRequest::removeRequest() { - SettingsObject(settingsKey()).undefine(); + } QString IncomingContactRequest::contactId() const @@ -286,7 +285,7 @@ void IncomingContactRequest::setChannel(Protocol::ContactRequestChannel *channel qDebug() << "Assigning connection to IncomingContactRequest from" << m_hostname; QSharedPointer newConnection = manager->contacts->identity->takeIncomingConnection(channel->connection()); if (!newConnection) { - BUG() << "Failed taking ownership of connection from an incoming request"; + TEGO_BUG() << "Failed taking ownership of connection from an incoming request"; channel->connection()->close(); return; } @@ -306,6 +305,9 @@ void IncomingContactRequest::setChannel(Protocol::ContactRequestChannel *channel connection = newConnection; + logger::println("nickname : {}", channel->nickname()); + logger::println("message : {}", channel->message()); + setNickname(channel->nickname()); setMessage(channel->message()); emit hasActiveConnectionChanged(); @@ -317,8 +319,9 @@ void IncomingContactRequest::accept(ContactUser *user) // Create the contact if necessary if (!user) { - Q_ASSERT(!nickname().isEmpty()); - user = manager->contacts->addContact(nickname()); + Q_ASSERT(nickname().isEmpty()); + this->setNickname("Mock Nickname"); + user = manager->contacts->addContact(QString::fromLatin1(m_hostname)); user->setHostname(QString::fromLatin1(m_hostname)); } @@ -346,6 +349,8 @@ void IncomingContactRequest::accept(ContactUser *user) void IncomingContactRequest::reject() { + logger::trace(); + qDebug() << "Rejecting contact request from" << m_hostname; using namespace Protocol::Data::ContactRequest; @@ -360,8 +365,6 @@ void IncomingContactRequest::reject() // Remove the request from the config removeRequest(); - // Blacklist the host to prevent repeat requests - manager->addRejectedHost(m_hostname); // Remove the request from the manager manager->removeRequest(this); diff --git a/src/core/IncomingRequestManager.h b/src/libtego/source/core/IncomingRequestManager.h similarity index 87% rename from src/core/IncomingRequestManager.h rename to src/libtego/source/core/IncomingRequestManager.h index 6c86f10f..1a2055b2 100644 --- a/src/core/IncomingRequestManager.h +++ b/src/libtego/source/core/IncomingRequestManager.h @@ -33,17 +33,13 @@ #ifndef INCOMINGREQUESTMANAGER_H #define INCOMINGREQUESTMANAGER_H -#include -#include -#include -#include "protocol/Connection.h" - class IncomingRequestManager; class ContactsManager; class ContactUser; namespace Protocol { class ContactRequestChannel; + class Connection; } class IncomingContactRequest : public QObject @@ -51,14 +47,6 @@ class IncomingContactRequest : public QObject Q_OBJECT Q_DISABLE_COPY(IncomingContactRequest) - Q_PROPERTY(QByteArray hostname READ hostname CONSTANT) - Q_PROPERTY(QString contactId READ contactId CONSTANT) - Q_PROPERTY(QString message READ message CONSTANT) - Q_PROPERTY(QString nickname READ nickname WRITE setNickname NOTIFY nicknameChanged) - Q_PROPERTY(bool hasActiveConnection READ hasActiveConnection NOTIFY hasActiveConnectionChanged) - Q_PROPERTY(QDateTime requestDate READ requestDate CONSTANT) - Q_PROPERTY(QDateTime lastRequestDate READ lastRequestDate CONSTANT) - public: IncomingRequestManager * const manager; @@ -84,7 +72,6 @@ class IncomingContactRequest : public QObject void renew(); - QString settingsKey() const; void load(); void save(); @@ -121,8 +108,6 @@ class IncomingRequestManager : public QObject Q_OBJECT Q_DISABLE_COPY(IncomingRequestManager) - Q_PROPERTY(QList requests READ requestObjects NOTIFY requestsChanged) - friend class IncomingContactRequest; public: @@ -140,9 +125,12 @@ class IncomingRequestManager : public QObject * configuration. */ void loadRequests(); + void loadRequests(const QList userHostnames); + /* Blacklist a host for immediate rejection in the future */ void addRejectedHost(const QByteArray &hostname); bool isHostnameRejected(const QByteArray &hostname) const; + QList getRejectedHostnames() const; signals: void requestAdded(IncomingContactRequest *request); @@ -154,6 +142,7 @@ private slots: private: QList m_requests; + QSet rejectedHosts; void removeRequest(IncomingContactRequest *request); }; diff --git a/src/core/OutgoingContactRequest.cpp b/src/libtego/source/core/OutgoingContactRequest.cpp similarity index 77% rename from src/core/OutgoingContactRequest.cpp rename to src/libtego/source/core/OutgoingContactRequest.cpp index 4def05f0..a2b9e046 100644 --- a/src/core/OutgoingContactRequest.cpp +++ b/src/libtego/source/core/OutgoingContactRequest.cpp @@ -37,55 +37,40 @@ #include "IncomingRequestManager.h" #include "utils/Useful.h" #include "protocol/ContactRequestChannel.h" -#include -OutgoingContactRequest *OutgoingContactRequest::createNewRequest(ContactUser *user, const QString &myNickname, - const QString &message) +#include "ed25519.hpp" +#include "context.hpp" +#include "user.hpp" +#include "globals.hpp" + +OutgoingContactRequest *OutgoingContactRequest::createNewRequest(ContactUser *user, const QString &message) { Q_ASSERT(!user->contactRequest()); - SettingsObject *settings = user->settings(); - settings->write("request.status", static_cast(Pending)); - settings->write("request.myNickname", myNickname); - settings->write("request.message", message); - - user->loadContactRequest(); + user->createContactRequest(message); Q_ASSERT(user->contactRequest()); + user->contactRequest()->setStatus(Pending); return user->contactRequest(); } -OutgoingContactRequest::OutgoingContactRequest(ContactUser *u) +OutgoingContactRequest::OutgoingContactRequest(ContactUser *u, const QString& msg) : QObject(u), user(u) - , m_settings(new SettingsObject(u->settings(), QStringLiteral("request"), this)) + , m_status(Pending) + , m_message(msg) { emit user->identity->contacts.outgoingRequestAdded(this); attemptAutoAccept(); } -OutgoingContactRequest::~OutgoingContactRequest() -{ - user->setProperty("contactRequest", QVariant()); -} - -QString OutgoingContactRequest::myNickname() const -{ - return m_settings->read("myNickname").toString(); -} - QString OutgoingContactRequest::message() const { - return m_settings->read("message").toString(); + return m_message; } OutgoingContactRequest::Status OutgoingContactRequest::status() const { - return static_cast(m_settings->read("status").toInt()); -} - -QString OutgoingContactRequest::rejectMessage() const -{ - return m_settings->read("rejectMessage").toString(); + return m_status; } void OutgoingContactRequest::setStatus(Status newStatus) @@ -94,7 +79,22 @@ void OutgoingContactRequest::setStatus(Status newStatus) if (newStatus == oldStatus) return; - m_settings->write("status", static_cast(newStatus)); + m_status = newStatus; + + if (m_status == Accepted || m_status == Error || m_status == Rejected) + { + // convert our hostname to just the service id raw string + auto serviceIdString = user->hostname().chopped(tego::static_strlen(".onion")).toUtf8(); + // ensure valid service id + auto serviceId = std::make_unique(serviceIdString.data(), serviceIdString.size()); + // create user id object from service id + auto userId = std::make_unique(*serviceId.get()); + + tego_bool_t requestAccepted = ((m_status == Accepted) ? TEGO_TRUE : TEGO_FALSE); + + tego::g_globals.context->callback_registry_.emit_chat_request_response_received(userId.release(), requestAccepted); + } + emit statusChanged(newStatus, oldStatus); } @@ -117,12 +117,12 @@ void OutgoingContactRequest::attemptAutoAccept() void OutgoingContactRequest::sendRequest(const QSharedPointer &connection) { if (connection != user->connection()) { - BUG() << "OutgoingContactRequest connection doesn't match the assigned user"; + TEGO_BUG() << "OutgoingContactRequest connection doesn't match the assigned user"; return; } if (connection->purpose() != Protocol::Connection::Purpose::OutboundRequest) { - BUG() << "OutgoingContactRequest told to use a connection of invalid purpose" << int(connection->purpose()); + TEGO_BUG() << "OutgoingContactRequest told to use a connection of invalid purpose" << int(connection->purpose()); return; } @@ -147,11 +147,9 @@ void OutgoingContactRequest::sendRequest(const QSharedPointersetMessage(message()); - if (!myNickname().isEmpty()) - channel->setNickname(myNickname()); if (!channel->openChannel()) { - BUG() << "Channel for outgoing contact request failed"; + TEGO_BUG() << "Channel for outgoing contact request failed"; return; } } @@ -165,7 +163,7 @@ void OutgoingContactRequest::removeRequest() } /* Clear the request settings */ - m_settings->undefine(); + logger::trace(); emit removed(); } @@ -176,9 +174,8 @@ void OutgoingContactRequest::accept() removeRequest(); } -void OutgoingContactRequest::reject(bool error, const QString &reason) +void OutgoingContactRequest::reject(bool error) { - m_settings->write("rejectMessage", reason); setStatus(error ? Error : Rejected); if (user->connection()) { @@ -187,7 +184,7 @@ void OutgoingContactRequest::reject(bool error, const QString &reason) channel->closeChannel(); } - emit rejected(reason); + emit rejected(); } void OutgoingContactRequest::cancel() @@ -206,13 +203,13 @@ void OutgoingContactRequest::requestStatusChanged(int status) accept(); break; case Response::Rejected: - reject(); + reject(false); break; case Response::Error: reject(true); break; default: - BUG() << "Unknown ContactRequest response status"; + TEGO_BUG() << "Unknown ContactRequest response status"; break; } } diff --git a/src/core/OutgoingContactRequest.h b/src/libtego/source/core/OutgoingContactRequest.h similarity index 80% rename from src/core/OutgoingContactRequest.h rename to src/libtego/source/core/OutgoingContactRequest.h index a8df18ed..d0050a32 100644 --- a/src/core/OutgoingContactRequest.h +++ b/src/libtego/source/core/OutgoingContactRequest.h @@ -33,9 +33,6 @@ #ifndef OUTGOINGCONTACTREQUEST_H #define OUTGOINGCONTACTREQUEST_H -#include -#include "utils/Settings.h" - class ContactUser; class ContactRequestClient; @@ -47,12 +44,6 @@ class OutgoingContactRequest : public QObject { Q_OBJECT Q_DISABLE_COPY(OutgoingContactRequest) - Q_ENUMS(Status) - - Q_PROPERTY(Status status READ status NOTIFY statusChanged) - Q_PROPERTY(QString myNickname READ myNickname CONSTANT) - Q_PROPERTY(QString message READ message CONSTANT) - Q_PROPERTY(QString rejectMessage READ rejectMessage NOTIFY rejected) public: enum Status @@ -65,21 +56,20 @@ class OutgoingContactRequest : public QObject FirstResult = Accepted }; - static OutgoingContactRequest *createNewRequest(ContactUser *user, const QString &myNickname, const QString &message); + static OutgoingContactRequest *createNewRequest(ContactUser *user, const QString &message); ContactUser * const user; - OutgoingContactRequest(ContactUser *user); - virtual ~OutgoingContactRequest(); + OutgoingContactRequest(ContactUser *user, const QString &message); + virtual ~OutgoingContactRequest() = default; QString myNickname() const; QString message() const; Status status() const; - QString rejectMessage() const; public slots: void accept(); - void reject(bool error = false, const QString &reason = QString()); + void reject(bool error); void cancel(); void sendRequest(const QSharedPointer &connection); @@ -87,14 +77,15 @@ public slots: signals: void statusChanged(int newStatus, int oldStatus); void accepted(); - void rejected(const QString &reason); + void rejected(); void removed(); private slots: void requestStatusChanged(int status); private: - SettingsObject *m_settings; + Status m_status; + QString m_message; void setStatus(Status newStatus); void removeRequest(); diff --git a/src/core/UserIdentity.cpp b/src/libtego/source/core/UserIdentity.cpp similarity index 67% rename from src/core/UserIdentity.cpp rename to src/libtego/source/core/UserIdentity.cpp index 2c1d6c17..8cdad4b4 100644 --- a/src/core/UserIdentity.cpp +++ b/src/libtego/source/core/UserIdentity.cpp @@ -30,100 +30,86 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include "signals.hpp" +#include "context.hpp" +#include "ed25519.hpp" +#include "globals.hpp" + +using tego::g_globals; + #include "UserIdentity.h" #include "tor/TorControl.h" #include "tor/HiddenService.h" #include "core/ContactIDValidator.h" +#include "core/ContactUser.h" #include "protocol/Connection.h" #include "utils/Useful.h" -#include -#include -#include -#include using namespace Protocol; -UserIdentity::UserIdentity(int id, QObject *parent) +UserIdentity::UserIdentity(int id, const QString& serviceID, QObject *parent) : QObject(parent) , uniqueID(id) , contacts(this) - , m_settings(0) , m_hiddenService(0) , m_incomingServer(0) { - m_settings = new SettingsObject(QStringLiteral("identity"), this); - connect(m_settings, &SettingsObject::modified, this, &UserIdentity::onSettingsModified); - - setupService(); - - contacts.loadFromSettings(); + setupService(serviceID); } -UserIdentity *UserIdentity::createIdentity(int uniqueID, const QString &dataDirectory) +UserIdentity *UserIdentity::createIdentity(int uniqueID) { // There is actually no support for multiple identities currently. Q_ASSERT(uniqueID == 0); if (uniqueID != 0) return 0; - SettingsObject settings(QStringLiteral("identity")); - settings.write("initializing", true); - if (dataDirectory.isEmpty()) - settings.write("dataDirectory", QString::fromLatin1("data-%1").arg(uniqueID)); - else - settings.write("dataDirectory", dataDirectory); - - return new UserIdentity(uniqueID); + return new UserIdentity(uniqueID, "", {}); } // TODO: Handle the error cases of this function in a useful way -void UserIdentity::setupService() +void UserIdentity::setupService(const QString& serviceID) { - QString keyData = m_settings->read("serviceKey").toString(); - QString legacyDir = m_settings->read("dataDirectory").toString(); + g_globals.context->set_host_user_state(tego_host_user_state_offline); + + QString keyData = serviceID; if (!keyData.isEmpty()) { CryptoKey key; - if (!key.loadFromData(QByteArray::fromBase64(keyData.toLatin1()), CryptoKey::PrivateKey, CryptoKey::DER)) { + if (!key.loadFromKeyBlob(keyData.toLatin1())) { qWarning() << "Cannot load service key from configuration"; return; } - - m_hiddenService = new Tor::HiddenService(key, legacyDir, this); - } else if (!legacyDir.isEmpty() && QFile::exists(legacyDir + QLatin1String("/private_key"))) { - qDebug() << "Attempting to load key from legacy filesystem format in" << legacyDir; - - CryptoKey key; - if (!key.loadFromFile(legacyDir + QLatin1String("/private_key"), CryptoKey::PrivateKey)) { - qWarning() << "Cannot load legacy format key from" << legacyDir << "for conversion"; - return; - } else { - keyData = QString::fromLatin1(key.encodedPrivateKey(CryptoKey::DER).toBase64()); - m_settings->write("serviceKey", keyData); - m_hiddenService = new Tor::HiddenService(key, legacyDir, this); - } - } else if (!m_settings->read("initializing").toBool()) { - qWarning() << "Missing private key for initialized identity"; - return; + m_hiddenService = new Tor::HiddenService(key, this); } else { - m_hiddenService = new Tor::HiddenService(legacyDir, this); + m_hiddenService = new Tor::HiddenService(this); connect(m_hiddenService, &Tor::HiddenService::privateKeyChanged, this, [&]() { - QString key = QString::fromLatin1(m_hiddenService->privateKey().encodedPrivateKey(CryptoKey::DER).toBase64()); - m_settings->write("serviceKey", key); + QString key = QString::fromLatin1(m_hiddenService->privateKey().encodedKeyBlob()); + const QByteArray rawKey = key.toUtf8(); + + // convert keyblob string to tego_ed25519_private key + std::unique_ptr privateKey; + tego_ed25519_private_key_from_ed25519_keyblob( + tego::out(privateKey), + rawKey.data(), + static_cast(rawKey.size()), + tego::throw_on_error()); + + g_globals.context->callback_registry_.emit_new_identity_created(privateKey.release()); } ); } + g_globals.context->set_host_user_state(tego_host_user_state_connecting); + Q_ASSERT(m_hiddenService); connect(m_hiddenService, SIGNAL(statusChanged(int,int)), SLOT(onStatusChanged(int,int))); // Generally, these are not used, and we bind to localhost and port 0 // for an automatic (and portable) selection. - QHostAddress address(m_settings->read("localListenAddress").toString()); - if (address.isNull()) - address = QHostAddress::LocalHost; - quint16 port = (quint16)m_settings->read("localListenPort").toInt(); + QHostAddress address = QHostAddress::LocalHost; + quint16 port = 0; m_incomingServer = new QTcpServer(this); if (!m_incomingServer->listen(address, port)) { @@ -135,12 +121,7 @@ void UserIdentity::setupService() connect(m_incomingServer, &QTcpServer::newConnection, this, &UserIdentity::onIncomingConnection); m_hiddenService->addTarget(9878, m_incomingServer->serverAddress(), m_incomingServer->serverPort()); - torControl->addHiddenService(m_hiddenService); -} - -SettingsObject *UserIdentity::settings() -{ - return m_settings; + g_globals.context->torControl->addHiddenService(m_hiddenService); } QString UserIdentity::hostname() const @@ -153,28 +134,10 @@ QString UserIdentity::contactID() const return ContactIDValidator::idFromHostname(hostname()); } -QString UserIdentity::nickname() const -{ - return m_settings->read("nickname").toString(); -} - -void UserIdentity::setNickname(const QString &nick) -{ - m_settings->write("nickname", nick); -} - -void UserIdentity::onSettingsModified(const QString &key, const QJsonValue &value) -{ - Q_UNUSED(value); - if (key == QLatin1String("nickname")) - emit nicknameChanged(); -} - void UserIdentity::onStatusChanged(int newStatus, int oldStatus) { if (oldStatus == Tor::HiddenService::NotCreated && newStatus > oldStatus) { - m_settings->write("initializing", QJsonValue::Undefined); emit contactIDChanged(); } emit statusChanged(); @@ -219,8 +182,8 @@ void UserIdentity::onIncomingConnection() */ connect(connPtr, &Connection::closed, this, [this,connPtr]() { - QSharedPointer conn(takeIncomingConnection(connPtr)); - if (conn) + QSharedPointer inconn(takeIncomingConnection(connPtr)); + if (inconn) qDebug() << "Deleting closed incoming connection that was never claimed by an owner"; } ); @@ -243,7 +206,7 @@ void UserIdentity::handleIncomingAuthedConnection(Connection *conn) QString clientName = conn->authenticatedIdentity(Connection::HiddenServiceAuth); if (clientName.isEmpty()) { - BUG() << "Called to handle incoming authed connection without any authed name"; + TEGO_BUG() << "Called to handle incoming authed connection without any authed name"; return; } @@ -257,11 +220,11 @@ void UserIdentity::handleIncomingAuthedConnection(Connection *conn) QSharedPointer connPtr(takeIncomingConnection(conn)); if (!connPtr) { - BUG() << "Called to handle incoming authed connection, but the connection is already out of the incoming list"; + TEGO_BUG() << "Called to handle incoming authed connection, but the connection is already out of the incoming list"; return; } - qDebug() << "Incoming connection authenticated as contact" << user->uniqueID << "with hostname" << clientName; + qDebug() << "Incoming connection authenticated as contact with hostname" << clientName; user->assignConnection(connPtr); } diff --git a/src/core/UserIdentity.h b/src/libtego/source/core/UserIdentity.h similarity index 78% rename from src/core/UserIdentity.h rename to src/libtego/source/core/UserIdentity.h index 457227ce..54f85517 100644 --- a/src/core/UserIdentity.h +++ b/src/libtego/source/core/UserIdentity.h @@ -34,10 +34,6 @@ #define USERIDENTITY_H #include "ContactsManager.h" -#include -#include -#include -#include namespace Tor { @@ -66,37 +62,24 @@ class UserIdentity : public QObject Q_DISABLE_COPY(UserIdentity) friend class IdentityManager; - - Q_PROPERTY(int uniqueID READ getUniqueID CONSTANT) - Q_PROPERTY(QString nickname READ nickname WRITE setNickname NOTIFY nicknameChanged) - Q_PROPERTY(QString contactID READ contactID NOTIFY contactIDChanged) - Q_PROPERTY(bool isOnline READ isServiceOnline NOTIFY statusChanged) - Q_PROPERTY(ContactsManager *contacts READ getContacts CONSTANT) - Q_PROPERTY(SettingsObject *settings READ settings CONSTANT) - public: const int uniqueID; ContactsManager contacts; - explicit UserIdentity(int uniqueID, QObject *parent = 0); + explicit UserIdentity(int uniqueID, const QString& serviceID, QObject *parent = 0); /* Properties */ int getUniqueID() const { return uniqueID; } - QString nickname() const; /* Hostname is .onion format, like ContactUser */ QString hostname() const; QString contactID() const; ContactsManager *getContacts() { return &contacts; } - void setNickname(const QString &nickname); - /* State */ bool isServiceOnline() const; Tor::HiddenService *hiddenService() const { return m_hiddenService; } - SettingsObject *settings(); - /* Take ownership of an inbound connection. Returns the shared pointer to * the connection, and releases the reference held by UserIdentity. */ QSharedPointer takeIncomingConnection(Protocol::Connection *connection); @@ -104,25 +87,21 @@ class UserIdentity : public QObject signals: void statusChanged(); void contactIDChanged(); // only possible during creation - void nicknameChanged(); - void settingsChanged(const QString &key); void incomingConnection(Protocol::Connection *connection); private slots: void onStatusChanged(int newStatus, int oldStatus); - void onSettingsModified(const QString &key, const QJsonValue &value); void onIncomingConnection(); private: - SettingsObject *m_settings; Tor::HiddenService *m_hiddenService; QTcpServer *m_incomingServer; QVector> m_incomingConnections; - static UserIdentity *createIdentity(int uniqueID, const QString &dataDirectory = QString()); + static UserIdentity *createIdentity(int uniqueID); void handleIncomingAuthedConnection(Protocol::Connection *connection); - void setupService(); + void setupService(const QString& serviceID); }; Q_DECLARE_METATYPE(UserIdentity*) diff --git a/src/libtego/source/delete.cpp b/src/libtego/source/delete.cpp new file mode 100644 index 00000000..8addc7c7 --- /dev/null +++ b/src/libtego/source/delete.cpp @@ -0,0 +1,24 @@ +#include "ed25519.hpp" +#include "error.hpp" +#include "context.hpp" +#include "tor.hpp" +#include "file_hash.hpp" + +extern "C" +{ + #define TEGO_DELETE_IMPL(TYPE)\ + void TYPE##_delete(TYPE##_t* obj)\ + {\ + delete obj;\ + } + + TEGO_DELETE_IMPL(tego_ed25519_private_key) + TEGO_DELETE_IMPL(tego_ed25519_public_key) + TEGO_DELETE_IMPL(tego_ed25519_signature) + TEGO_DELETE_IMPL(tego_v3_onion_service_id) + TEGO_DELETE_IMPL(tego_error) + TEGO_DELETE_IMPL(tego_tor_launch_config) + TEGO_DELETE_IMPL(tego_tor_daemon_config) + TEGO_DELETE_IMPL(tego_user_id) + TEGO_DELETE_IMPL(tego_file_hash) +} diff --git a/src/libtego/source/ed25519.cpp b/src/libtego/source/ed25519.cpp new file mode 100644 index 00000000..c6c30820 --- /dev/null +++ b/src/libtego/source/ed25519.cpp @@ -0,0 +1,465 @@ +#include "error.hpp" +#include "ed25519.hpp" + +// header of ed25519 KeyBlob returned by ADD_ONION comand +#define TEGO_ED25519_KEYBLOB_HEADER "ED25519-V3:" +// length of ed25519 KeyBlob header string not including null terminator +constexpr size_t TEGO_ED25519_KEYBLOB_HEADER_LENGTH = tego::static_strlen(TEGO_ED25519_KEYBLOB_HEADER); +// length of ed25519 KeyBlob string not including null terminator +constexpr size_t TEGO_ED25519_KEYBLOB_BASE64_LENGTH = 88; +// number of bytes needed to encode KeyBlob including the null terminator +constexpr size_t TEGO_ED25519_KEYBLOB_BASE64_SIZE = TEGO_ED25519_KEYBLOB_BASE64_LENGTH + 1; +// number of bytes the base32 encoded service id string decodes to +constexpr size_t TEGO_V3_ONION_SERVICE_ID_RAW_SIZE = 35; +// offset to public key in raw service id +constexpr size_t TEGO_V3_ONION_SERVICE_ID_PUBLIC_KEY_OFFSET = 0; +// length of public key in raw service id +constexpr size_t TEGO_V3_ONION_SERVICE_ID_PUBLIC_KEY_SIZE = 32; +// offset to checksum in raw service id +constexpr size_t TEGO_V3_ONION_SERVICE_ID_CHECKSUM_OFFSET = 32; +// length of checksum in raw service id +constexpr size_t TEGO_V3_ONION_SERVICE_ID_CHECKSUM_SIZE = 2; +// offset to version (which should be 0x03) in raw service id +constexpr size_t TEGO_V3_ONION_SERVICE_ID_VERSION_OFFSET = 34; +// prefix used when calculating service id checksum +#define TEGO_V3_ONION_SERVICE_ID_CHECKSUM_SRC_PREFIX ".onion checksum" +// length of service id prefix not including null terminator +constexpr size_t TEGO_V3_ONION_SERVICE_ID_CHECKSUM_SRC_PREFIX_LEGNTH = tego::static_strlen(TEGO_V3_ONION_SERVICE_ID_CHECKSUM_SRC_PREFIX); +// offset to the public key in the service id checksum source +constexpr size_t TEGO_V3_ONION_SERVICE_ID_CHECKSUM_SRC_PUBLIC_KEY_OFFSET = TEGO_V3_ONION_SERVICE_ID_CHECKSUM_SRC_PREFIX_LEGNTH; +// size of source data used to calculate the serice id checksum +constexpr size_t TEGO_V3_ONION_SERVICE_ID_CHECKSUM_SRC_SIZE = TEGO_V3_ONION_SERVICE_ID_CHECKSUM_SRC_PREFIX_LEGNTH + ED25519_PUBKEY_LEN + 1; +// offset for the version byte in the service id checksum source +constexpr size_t TEGO_V3_ONION_SERVICE_ID_CHECKSUM_SRC_VERSION_OFFSET = TEGO_V3_ONION_SERVICE_ID_CHECKSUM_SRC_SIZE - 1; + +namespace tego +{ + void truncated_checksum_from_ed25519_public_key( + uint8_t out_truncatedChecksum[TEGO_V3_ONION_SERVICE_ID_CHECKSUM_SIZE], + const uint8_t (&publicKey)[ED25519_PUBKEY_LEN]) + { + // build message for checksum + // prefix + uint8_t checksumSrc[TEGO_V3_ONION_SERVICE_ID_CHECKSUM_SRC_SIZE] = TEGO_V3_ONION_SERVICE_ID_CHECKSUM_SRC_PREFIX; + // public key + std::copy( + std::begin(publicKey), + std::begin(publicKey) + sizeof(publicKey), + checksumSrc + TEGO_V3_ONION_SERVICE_ID_CHECKSUM_SRC_PUBLIC_KEY_OFFSET); + // version byte 0x03 + checksumSrc[TEGO_V3_ONION_SERVICE_ID_CHECKSUM_SRC_VERSION_OFFSET] = 0x03; + + // verify checksum + uint8_t checksum[BASE32_DIGEST_LEN] = {0}; + + // calculate sha256 + // TODO: probably just call openssl APIs directly here rather than use tor's + // encapsulation to simplify build and include weirdness in precomp.h + TEGO_THROW_IF_FALSE(crypto_digest256( + reinterpret_cast(checksum), + reinterpret_cast(checksumSrc), + sizeof(checksumSrc), + DIGEST_SHA3_256) == 0); + + out_truncatedChecksum[0] = checksum[0]; + out_truncatedChecksum[1] = checksum[1]; + } +} + +tego_v3_onion_service_id::tego_v3_onion_service_id( + const char* serviceIdString, + size_t serviceIdStringLength) +{ + TEGO_THROW_IF_NULL(serviceIdString); + TEGO_THROW_IF_FALSE(serviceIdStringLength >= TEGO_V3_ONION_SERVICE_ID_LENGTH); + + std::string_view serviceIdView(serviceIdString, TEGO_V3_ONION_SERVICE_ID_LENGTH); + TEGO_THROW_IF_FALSE(is_valid(serviceIdView)); + // copy to our internal buffer + std::copy( + std::begin(serviceIdView), + std::end(serviceIdView), + this->data); +} + +tego_bool_t tego_v3_onion_service_id::is_valid( + std::string_view &serviceIdString) +{ + if (serviceIdString.size() != TEGO_V3_ONION_SERVICE_ID_LENGTH) + { + return TEGO_FALSE; + } + + uint8_t decodedServiceId[TEGO_V3_ONION_SERVICE_ID_RAW_SIZE] = {0}; + + // base32 decode service serviceId + const auto bytesDecoded = ::base32_decode( + reinterpret_cast(decodedServiceId), + sizeof(decodedServiceId), + serviceIdString.data(), + serviceIdString.size()); + + // check successful base32 decode and correct version byte + if (bytesDecoded != sizeof(decodedServiceId) || + decodedServiceId[TEGO_V3_ONION_SERVICE_ID_VERSION_OFFSET] != 0x03) + { + return TEGO_FALSE; + } + + auto& rawPublicKey = reinterpret_cast(decodedServiceId); + + // calculate the truncated checksum for the public key + uint8_t truncatedChecksum[TEGO_V3_ONION_SERVICE_ID_CHECKSUM_SIZE] = {0}; + tego::truncated_checksum_from_ed25519_public_key(truncatedChecksum, rawPublicKey); + + // verify the first two bytes of checksum in service id match our calculated checksum + auto validChecksum = decodedServiceId[TEGO_V3_ONION_SERVICE_ID_CHECKSUM_OFFSET ] == truncatedChecksum[0] && + decodedServiceId[TEGO_V3_ONION_SERVICE_ID_CHECKSUM_OFFSET + 1] == truncatedChecksum[1]; + + if (!validChecksum) + { + return TEGO_FALSE; + } + + return TEGO_TRUE; +} + +// +// Exports +// + +extern "C" +{ + void tego_ed25519_private_key_from_ed25519_keyblob( + tego_ed25519_private_key_t** out_privateKey, + const char* keyBlob, + size_t keyBlobLength, + tego_error_t** error) + { + return tego::translateExceptions([&]() -> void + { + // verify arguments + TEGO_THROW_IF_FALSE(out_privateKey != nullptr); + TEGO_THROW_IF_FALSE(*out_privateKey == nullptr); + TEGO_THROW_IF_FALSE(keyBlob != nullptr); + TEGO_THROW_IF_FALSE(keyBlobLength == TEGO_ED25519_KEYBLOB_LENGTH); + + // ensure KeyBlob starts with correct string constant + TEGO_THROW_IF_FALSE(std::string_view(keyBlob).starts_with(TEGO_ED25519_KEYBLOB_HEADER)); + + // get a string_view of the base64 blob + std::string_view base64(keyBlob + TEGO_ED25519_KEYBLOB_HEADER_LENGTH); + + // make sure the blob has enough characters to encode our privat ekey + const auto maxByteCount = ::base64_decode_maxsize(base64.size()); + TEGO_THROW_IF_FALSE(maxByteCount >= ED25519_SECKEY_LEN); + + // local buffer for private key + uint8_t privateKeyData[ED25519_SECKEY_LEN] = {0}; + const auto bytesWritten = ::base64_decode(reinterpret_cast(privateKeyData), sizeof(privateKeyData), base64.data(), base64.size()); + TEGO_THROW_IF_FALSE(bytesWritten == ED25519_SECKEY_LEN); + + // copy into returned tego_ed25519_private_key struct + auto privateKey = std::make_unique(); + std::copy(std::begin(privateKeyData), std::end(privateKeyData), privateKey->data); + + *out_privateKey = privateKey.release(); + }, error); + } + + size_t tego_ed25519_keyblob_from_ed25519_private_key( + char* out_keyBlob, + size_t keyBlobSize, + const tego_ed25519_private_key_t* privateKey, + tego_error_t** error) + { + return tego::translateExceptions([&]() -> size_t + { + // verify arguments + TEGO_THROW_IF_FALSE(out_keyBlob != nullptr); + TEGO_THROW_IF_FALSE(keyBlobSize >= TEGO_ED25519_KEYBLOB_SIZE); + TEGO_THROW_IF_FALSE(privateKey != nullptr); + + // zero out output buffer first + std::fill(out_keyBlob, out_keyBlob + keyBlobSize, 0x00); + + // init KeyBlob buffer with the header + char keyBlob[TEGO_ED25519_KEYBLOB_SIZE] = TEGO_ED25519_KEYBLOB_HEADER; + + // encode privatekey as base64 (adds null terminator) + auto base64BytesWritten = base64_encode( + keyBlob + TEGO_ED25519_KEYBLOB_HEADER_LENGTH, + TEGO_ED25519_KEYBLOB_BASE64_SIZE, + reinterpret_cast(privateKey->data), + sizeof(privateKey->data), + 0); + + TEGO_THROW_IF_FALSE(keyBlob[TEGO_ED25519_KEYBLOB_LENGTH] == 0); + TEGO_THROW_IF_FALSE(base64BytesWritten == TEGO_ED25519_KEYBLOB_BASE64_LENGTH); + + // copy entire KeyBlob to output buffer + std::copy(std::begin(keyBlob), std::end(keyBlob), out_keyBlob); + return sizeof(keyBlob); + }, error, 0); + } + + void tego_ed25519_public_key_from_ed25519_private_key( + tego_ed25519_public_key_t** out_publicKey, + const tego_ed25519_private_key_t* privateKey, + tego_error_t** error) + { + return tego::translateExceptions([&]() -> void + { + // verify arguments + TEGO_THROW_IF_FALSE(out_publicKey != nullptr); + TEGO_THROW_IF_FALSE(*out_publicKey == nullptr); + TEGO_THROW_IF_FALSE(privateKey != nullptr); + + // local buffer for public key + uint8_t publicKeyData[ED25519_PUBKEY_LEN] = {0}; + TEGO_THROW_IF_FALSE(ed25519_donna_pubkey(publicKeyData, privateKey->data) == 0); + + // copy into returned tego_ed25519_public_key struct + auto publicKey = std::make_unique(); + std::copy(std::begin(publicKeyData), std::end(publicKeyData), publicKey->data); + + *out_publicKey = publicKey.release(); + }, error); + } + + tego_bool_t tego_v3_onion_service_id_string_is_valid( + const char* serviceIdString, + size_t serviceIdStringLength, + tego_error_t** error) + { + return tego::translateExceptions([&]() -> tego_bool_t + { + TEGO_THROW_IF_NULL(serviceIdString); + TEGO_THROW_IF_FALSE(serviceIdStringLength >= TEGO_V3_ONION_SERVICE_ID_LENGTH); + + std::string_view serviceIdView(serviceIdString, TEGO_V3_ONION_SERVICE_ID_LENGTH); + return tego_v3_onion_service_id::is_valid(serviceIdView); + }, error, TEGO_FALSE); + } + + void tego_v3_onion_service_id_from_string( + tego_v3_onion_service_id_t** out_serviceId, + const char* serviceIdString, + size_t serviceIdStringLength, + tego_error_t** error) + { + return tego::translateExceptions([&]() -> void + { + TEGO_THROW_IF_FALSE(out_serviceId != nullptr); + TEGO_THROW_IF_FALSE(*out_serviceId == nullptr); + + auto serviceId = std::make_unique(serviceIdString, serviceIdStringLength); + *out_serviceId = serviceId.release(); + }, error); + } + + size_t tego_v3_onion_service_id_to_string( + const tego_v3_onion_service_id_t* serviceId, + char* out_serviceIdString, + size_t serviceIdStringSize, + tego_error_t** error) + { + return tego::translateExceptions([&]() -> size_t + { + // verify arguments + TEGO_THROW_IF_FALSE(serviceId != nullptr); + TEGO_THROW_IF_FALSE(out_serviceIdString != nullptr); + TEGO_THROW_IF_FALSE(serviceIdStringSize >= TEGO_V3_ONION_SERVICE_ID_SIZE); + + std::copy( + std::begin(serviceId->data), + std::end(serviceId->data), + out_serviceIdString); + + return TEGO_V3_ONION_SERVICE_ID_SIZE; + }, error, 0); + } + + void tego_ed25519_public_key_from_v3_onion_service_id( + tego_ed25519_public_key_t** out_publicKey, + const tego_v3_onion_service_id_t* serviceId, + tego_error_t** error) + { + return tego::translateExceptions([&]() -> void + { + // verify arguments + TEGO_THROW_IF_FALSE(out_publicKey != nullptr); + TEGO_THROW_IF_FALSE(*out_publicKey == nullptr); + TEGO_THROW_IF_FALSE(serviceId != nullptr); + + // https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt#n2135 + std::string_view serviceIdView(serviceId->data, TEGO_V3_ONION_SERVICE_ID_LENGTH); + uint8_t rawServiceId[TEGO_V3_ONION_SERVICE_ID_RAW_SIZE] = {0}; + + // base32 decode service id + const auto bytesDecoded = ::base32_decode( + reinterpret_cast(rawServiceId), + sizeof(rawServiceId), + serviceIdView.data(), + serviceIdView.size()); + TEGO_THROW_IF_FALSE(bytesDecoded == sizeof(rawServiceId)); + + // first part of the service id is the public key + + // copy over public key + auto publicKey = std::make_unique(); + std::copy(std::begin(rawServiceId), + std::begin(rawServiceId) + ED25519_PUBKEY_LEN, + publicKey->data); + + *out_publicKey = publicKey.release(); + }, error); + } + + void tego_v3_onion_service_id_from_ed25519_public_key( + tego_v3_onion_service_id_t** out_serviceId, + const tego_ed25519_public_key_t* publicKey, + tego_error_t** error) + { + return tego::translateExceptions([&]() -> void + { + // verify arguments + TEGO_THROW_IF_FALSE(out_serviceId != nullptr); + TEGO_THROW_IF_FALSE(*out_serviceId == nullptr); + TEGO_THROW_IF_FALSE(publicKey != nullptr); + + // build the raw service id + uint8_t rawServiceId[TEGO_V3_ONION_SERVICE_ID_RAW_SIZE] = {0}; + + // copy over public key + std::copy(std::begin(publicKey->data), + std::end(publicKey->data), + rawServiceId); + + // calculate turncated checksum and copy it into raw service id + uint8_t truncatedChecksum[TEGO_V3_ONION_SERVICE_ID_CHECKSUM_SIZE] = {0}; + tego::truncated_checksum_from_ed25519_public_key(truncatedChecksum, publicKey->data); + std::copy(std::begin(truncatedChecksum), + std::end(truncatedChecksum), + rawServiceId + TEGO_V3_ONION_SERVICE_ID_CHECKSUM_OFFSET); + + // version 3 + rawServiceId[TEGO_V3_ONION_SERVICE_ID_VERSION_OFFSET] = 0x03; + + // encode to base32 + char serviceIdString[TEGO_V3_ONION_SERVICE_ID_SIZE] = {0}; + ::base32_encode(serviceIdString, sizeof(serviceIdString), reinterpret_cast(rawServiceId), sizeof(rawServiceId)); + + auto serviceId = std::make_unique(); + std::copy(std::begin(serviceIdString), std::end(serviceIdString), serviceId->data); + + *out_serviceId = serviceId.release(); + }, error); + } + + void tego_ed25519_signature_from_bytes( + tego_ed25519_signature_t** out_signature, + const uint8_t* buffer, + size_t bufferSize, + tego_error_t** error) + { + return tego::translateExceptions([&]() -> void + { + // verify arguments + TEGO_THROW_IF_FALSE(out_signature != nullptr); + TEGO_THROW_IF_FALSE(*out_signature == nullptr); + TEGO_THROW_IF_FALSE(buffer != nullptr); + TEGO_THROW_IF_FALSE(bufferSize >= TEGO_ED25519_SIGNATURE_SIZE); + + // copy raw signature into signature struct + auto signature = std::make_unique(); + std::copy(buffer, buffer + TEGO_ED25519_SIGNATURE_SIZE, signature->data); + + *out_signature = signature.release(); + + }, error); + } + + size_t tego_ed25519_signature_to_bytes( + const tego_ed25519_signature_t* signature, + uint8_t* out_buffer, + size_t bufferSize, + tego_error_t** error) + { + return tego::translateExceptions([&]() -> size_t + { + TEGO_THROW_IF_FALSE(signature != nullptr); + TEGO_THROW_IF_FALSE(out_buffer != nullptr); + TEGO_THROW_IF_FALSE(bufferSize >= TEGO_ED25519_SIGNATURE_SIZE) + + // get the data out of our signature type + std::copy(std::begin(signature->data), std::end(signature->data), out_buffer); + return sizeof(signature->data); + }, error, 0); + } + + void tego_message_ed25519_sign( + const uint8_t* message, + size_t messageSize, + const tego_ed25519_private_key_t* privateKey, + const tego_ed25519_public_key_t* publicKey, + tego_ed25519_signature_t** out_signature, + tego_error_t** error) + { + return tego::translateExceptions([&]() -> void + { + // verify arguments + TEGO_THROW_IF_FALSE(message != nullptr); + TEGO_THROW_IF_FALSE(messageSize > 0); + TEGO_THROW_IF_FALSE(privateKey != nullptr); + TEGO_THROW_IF_FALSE(publicKey != nullptr); + TEGO_THROW_IF_FALSE(out_signature != nullptr); + TEGO_THROW_IF_FALSE(*out_signature == nullptr); + + // calculate message signature + uint8_t signatureBuffer[TEGO_ED25519_SIGNATURE_SIZE] = {0}; + TEGO_THROW_IF_FALSE( + ::ed25519_donna_sign( + signatureBuffer, + message, + messageSize, + privateKey->data, + publicKey->data) == 0); + + auto signature = std::make_unique(); + std::copy(std::begin(signatureBuffer), std::end(signatureBuffer), signature->data); + + *out_signature = signature.release(); + }, error); + } + + int tego_ed25519_signature_verify( + const tego_ed25519_signature_t* signature, + const uint8_t* message, + size_t messageSize, + const tego_ed25519_public_key_t* publicKey, + tego_error_t** error) + { + return tego::translateExceptions([&]() -> int + { + // verify arguments + TEGO_THROW_IF_FALSE(signature != nullptr); + TEGO_THROW_IF_FALSE(message != nullptr); + TEGO_THROW_IF_FALSE(messageSize > 0); + TEGO_THROW_IF_FALSE(publicKey != nullptr); + + // attempt to verify + auto result = ::ed25519_donna_open( + signature->data, + message, + messageSize, + publicKey->data); + + // result will be 0 if valid, -1 if not + TEGO_THROW_IF_FALSE(result == 0 || result == -1); + + if (result == 0) return TEGO_TRUE; + return TEGO_FALSE; + }, error, TEGO_FALSE); + } +} diff --git a/src/libtego/source/ed25519.hpp b/src/libtego/source/ed25519.hpp new file mode 100644 index 00000000..dd204c05 --- /dev/null +++ b/src/libtego/source/ed25519.hpp @@ -0,0 +1,27 @@ +#pragma once + +static_assert(ED25519_SIG_LEN == TEGO_ED25519_SIGNATURE_SIZE); + +struct tego_ed25519_private_key +{ + uint8_t data[ED25519_SECKEY_LEN] = {0}; +}; + +struct tego_ed25519_public_key +{ + uint8_t data[ED25519_PUBKEY_LEN] = {0}; +}; + +struct tego_ed25519_signature +{ + uint8_t data[ED25519_SIG_LEN] = {0}; +}; + +struct tego_v3_onion_service_id +{ + tego_v3_onion_service_id() = default; + tego_v3_onion_service_id(const char* serviceIdString, size_t serviceIdStringLength); + static tego_bool_t is_valid(std::string_view &serviceIdString); + + char data[TEGO_V3_ONION_SERVICE_ID_SIZE] = {0}; +}; diff --git a/src/libtego/source/error.cpp b/src/libtego/source/error.cpp new file mode 100644 index 00000000..26c91351 --- /dev/null +++ b/src/libtego/source/error.cpp @@ -0,0 +1,9 @@ +#include "error.hpp" + +extern "C" +{ + const char* tego_error_get_message(const tego_error_t* error) + { + return error->message.c_str(); + } +} \ No newline at end of file diff --git a/src/libtego/source/error.hpp b/src/libtego/source/error.hpp new file mode 100644 index 00000000..61de02cb --- /dev/null +++ b/src/libtego/source/error.hpp @@ -0,0 +1,60 @@ +#pragma once + +#define TEGO_STRINGIFY_IMPL(X) #X +#define TEGO_STRINGIFY(X) TEGO_STRINGIFY_IMPL(X) + +#define TEGO_THROW_MSG(FMT, ...) throw std::runtime_error(fmt::format("runtime error " __FILE__ ":" TEGO_STRINGIFY(__LINE__) " " FMT __VA_OPT__(,) __VA_ARGS__)); + +#define TEGO_THROW_IF_FALSE_MSG(B, ...) if (!(B)) { TEGO_THROW_MSG(__VA_ARGS__); } +#define TEGO_THROW_IF_FALSE(B) TEGO_THROW_IF_FALSE_MSG(B, "{} must be true", TEGO_STRINGIFY(B)) + +#define TEGO_THROW_IF_TRUE_MSG(B, ...) if (B) { TEGO_THROW_MSG("{}", __VA_ARGS__); } +#define TEGO_THROW_IF_TRUE(B) TEGO_THROW_IF_TRUE_MSG(B, "{} must be false", TEGO_STRINGIFY(B)) + +#define TEGO_THROW_IF_NULL(PTR) TEGO_THROW_IF_FALSE_MSG((PTR != nullptr), "{} must not be null", TEGO_STRINGIFY(PTR)) +#define TEGO_THROW_IF_NOT_NULL(PTR) TEGO_THROW_IF_FALSE_MSG((PTR == nullptr), "{} must be null", TEGO_STRINGIFY(PTR)) + +struct tego_error +{ + std::string message; +}; + +namespace tego +{ + template + auto translateExceptions(FUNC&& fn, tego_error_t** out_error) noexcept(true) -> void + { + static_assert(std::is_same::value); + + try + { + fn(); + } + catch(const std::exception& ex) + { + if (out_error) + { + logger::println("Exception: {}", ex.what()); + *out_error = new tego_error{ex.what()}; + } + } + } + + template + auto translateExceptions(FUNC&& fn, tego_error_t** out_error, decltype(fn()) onErrorReturn) noexcept(true) -> decltype(fn()) + { + try + { + return fn(); + } + catch(const std::exception& ex) + { + if (out_error) + { + logger::println("Exception: {}", ex.what()); + *out_error = new tego_error{ex.what()}; + } + } + return onErrorReturn; + } +} \ No newline at end of file diff --git a/src/libtego/source/file_hash.cpp b/src/libtego/source/file_hash.cpp new file mode 100644 index 00000000..21fa3a5e --- /dev/null +++ b/src/libtego/source/file_hash.cpp @@ -0,0 +1,121 @@ +#include "file_hash.hpp" +#include "error.hpp" + +// implements deleter for openssl's EVP_MD_CTX +namespace std +{ + template<> class default_delete<::EVP_MD_CTX> + { + public: + void operator()(EVP_MD_CTX* val) + { + ::EVP_MD_CTX_free(val); + } + }; +} + +tego_file_hash::tego_file_hash() +{ + TEGO_THROW_IF_FALSE(static_cast(EVP_MD_size(EVP_sha3_512())) == data.size()); + data.fill(uint8_t(0x00)); +} + + +tego_file_hash::tego_file_hash(uint8_t const* begin, uint8_t const* end) +: tego_file_hash() +{ + // init sha3 512 algo + std::unique_ptr<::EVP_MD_CTX> ctx(EVP_MD_CTX_new()); + EVP_DigestInit_ex(ctx.get(), EVP_sha3_512(), nullptr); + + // calc hash + EVP_DigestUpdate(ctx.get(), begin, static_cast(end - begin)); + + // copy hash to our local buffer + uint32_t hashSize = 0; + EVP_DigestFinal_ex(ctx.get(), data.begin(), &hashSize); + TEGO_THROW_IF_FALSE(hashSize == this->DIGEST_SIZE); +} + +tego_file_hash::tego_file_hash(std::istream& stream) +: tego_file_hash() +{ + // init sha3 512 algo + std::unique_ptr<::EVP_MD_CTX> ctx(EVP_MD_CTX_new()); + EVP_DigestInit_ex(ctx.get(), EVP_sha3_512(), nullptr); + + // alloc a temp 64k buffer to read bytes into + constexpr size_t BLOCK_SIZE = 65536; + auto buffer = std::make_unique(BLOCK_SIZE); + + // read and hash bytes + while(stream.good()) + { + // read bytes into buffer + stream.read(buffer.get(), BLOCK_SIZE); + const auto bytesRead = static_cast(stream.gcount()); + TEGO_THROW_IF_FALSE_MSG(bytesRead <= BLOCK_SIZE, "Invalid amount of bytes read"); + + // hash the block + EVP_DigestUpdate(ctx.get(), buffer.get(), bytesRead); + } + + // copy hash to our local buffer + uint32_t hashSize = 0; + EVP_DigestFinal_ex(ctx.get(), data.begin(), &hashSize); + TEGO_THROW_IF_FALSE(hashSize == this->DIGEST_SIZE); +} + +constexpr size_t tego_file_hash::string_size() const +{ + return STRING_SIZE; +} + +const std::string& tego_file_hash::to_string() const +{ + if (hex.empty()) + { + std::stringstream ss; + for(auto byte : data) + { + fmt::print(ss, "{:02x}", byte); + } + hex = std::move(ss.str()); + } + return hex; +} + +extern "C" +{ + size_t tego_file_hash_string_size( + tego_file_hash_t const* fileHash, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> size_t + { + TEGO_THROW_IF_NULL(fileHash); + return fileHash->string_size(); + }, error, 0); + } + + size_t tego_file_hash_to_string( + tego_file_hash_t const* fileHash, + char* out_hashString, + size_t hashStringSize, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> size_t + { + TEGO_THROW_IF_NULL(fileHash); + TEGO_THROW_IF_NULL(out_hashString); + TEGO_THROW_IF_FALSE(hashStringSize >= fileHash->string_size()); + + const auto& hashString = fileHash->to_string(); + std::copy(hashString.begin(), hashString.end(), out_hashString); + // null terminator + out_hashString[fileHash->string_size() - 1] = 0; + + return fileHash->string_size(); + }, error, 0); + } +} diff --git a/src/libtego/source/file_hash.hpp b/src/libtego/source/file_hash.hpp new file mode 100644 index 00000000..a65d2a2f --- /dev/null +++ b/src/libtego/source/file_hash.hpp @@ -0,0 +1,26 @@ +#pragma once + +// +// Tego File Hash +// + +struct tego_file_hash +{ + tego_file_hash(); + // hash a blob of memory + tego_file_hash(uint8_t const* begin, uint8_t const* end); + // hash en entire stream, reads bytes into blocks and incrementally hashes + tego_file_hash(std::istream& stream); + + constexpr size_t string_size() const; + const std::string& to_string() const; + + // 512 bits, 8 bits per byte + constexpr static size_t SHA3_512_DIGEST_SIZE = 512 / 8; + constexpr static size_t DIGEST_SIZE = SHA3_512_DIGEST_SIZE; + // two chars per byte plus null terminator + constexpr static size_t STRING_LENGTH = DIGEST_SIZE * 2; + constexpr static size_t STRING_SIZE = STRING_LENGTH + 1; + std::array data; + mutable std::string hex; +}; \ No newline at end of file diff --git a/src/libtego/source/globals.cpp b/src/libtego/source/globals.cpp new file mode 100644 index 00000000..7a524d01 --- /dev/null +++ b/src/libtego/source/globals.cpp @@ -0,0 +1,3 @@ +#include "globals.hpp" + +tego::globals tego::globals::instance = {}; \ No newline at end of file diff --git a/src/libtego/source/globals.hpp b/src/libtego/source/globals.hpp new file mode 100644 index 00000000..04f0f910 --- /dev/null +++ b/src/libtego/source/globals.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "context.hpp" + +namespace tego +{ + // dumping ground for global flags and variables + struct globals + { + globals() = default; + + bool opensslAllocatorInited = false; + bool secureRNGSeeded = false; + std::unique_ptr context = nullptr; + + static globals instance; + }; + + inline constexpr globals& g_globals = globals::instance; +} \ No newline at end of file diff --git a/src/libtego/source/libtego.cpp b/src/libtego/source/libtego.cpp new file mode 100644 index 00000000..5847b8c2 --- /dev/null +++ b/src/libtego/source/libtego.cpp @@ -0,0 +1,58 @@ +#include "error.hpp" +#include "context.hpp" +#include "globals.hpp" +using namespace tego; + +#include "utils/SecureRNG.h" + +// +// Exports +// + +extern "C" +{ + void tego_initialize(tego_context_t** out_context, tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + logger::println("init"); + + TEGO_THROW_IF_NULL(out_context); + TEGO_THROW_IF_FALSE(tego::g_globals.context.get() == nullptr); + + // initialize OpenSSL's allocator + if (!g_globals.opensslAllocatorInited) { + #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) + CRYPTO_malloc_init(); + #else + OPENSSL_malloc_init(); + #endif + + g_globals.opensslAllocatorInited = true; + } + + // seed our secure RNG + if (!g_globals.secureRNGSeeded) + { + TEGO_THROW_IF_FALSE_MSG(SecureRNG::seed(), "Failed to initialize RNG"); + g_globals.secureRNGSeeded = true; + } + + // create and save off singleton context + g_globals.context = std::make_unique(); + *out_context = g_globals.context.get(); + + }, error); + } + + void tego_uninitialize(tego_context_t* context, tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + if (context) + { + tego::g_globals.context.reset(nullptr); + } + }, error); + } +} \ No newline at end of file diff --git a/src/libtego/source/logger.cpp b/src/libtego/source/logger.cpp new file mode 100644 index 00000000..59700c60 --- /dev/null +++ b/src/libtego/source/logger.cpp @@ -0,0 +1,109 @@ +#ifdef ENABLE_TEGO_LOGGER + +// +// logger methods +// + +void logger::trace(const source_location& loc) +{ + println("{}:{} -> {}(...)", loc.file_name(), loc.line(), loc.function_name()); +} + +std::ofstream& logger::get_stream() +{ + static std::ofstream fs("libtego.log", std::ios::binary); + return fs; +} + +std::mutex& logger::get_mutex() +{ + static std::mutex m; + return m; +} + +double logger::get_timestamp() +{ + const static auto start = std::chrono::system_clock::now(); + const auto now = std::chrono::system_clock::now(); + std::chrono::duration duration(now - start); + return duration.count(); +} +#endif + +// +// std::ostream << operators +// + +std::ostream& operator<<(std::ostream& out, const QString& str) +{ + auto utf8str = str.toUtf8(); + out << utf8str.constData(); + return out; +} + +// hex dump QByteArray +std::ostream& operator<<(std::ostream& out, const QByteArray& blob) +{ + constexpr size_t rowWidth = 32; + const size_t rowCount = static_cast(blob.size()) / rowWidth; + + const char* head = blob.data(); + size_t address = 0; + + out << '\n'; + + auto printRow = [&](size_t count) -> void + { + constexpr auto octetGrouping = 4; + fmt::print(out, "{:08x} : ", address); + for(size_t k = 0; k < count; k++) + { + if ((k % octetGrouping) == 0) { + fmt::print(out, " "); + } + fmt::print(out, "{:02x}", static_cast(head[k])); + } + for(size_t k = count; k < rowWidth; k++) + { + if ((k % octetGrouping) == 0) { + fmt::print(out, " "); + } + fmt::print(out, ".."); + } + + fmt::print(out, " | "); + for(size_t k = 0; k < count; k++) + { + char c = head[k]; + if (std::isprint(c)) + { + fmt::print(out, "{}", c); + } + else + { + fmt::print(out, "."); + } + } + + out << '\n'; + + address += rowWidth; + head += rowWidth; + }; + + // foreach row + for(size_t i = 0; i < rowCount; i++) + { + printRow(rowWidth); + + } + + // remainder + const size_t remainder = (static_cast(blob.size()) % rowWidth); + if (remainder > 0) + { + printRow(remainder); + } + + return out; +} diff --git a/src/libtego/source/orconfig.h b/src/libtego/source/orconfig.h new file mode 100644 index 00000000..788edbeb --- /dev/null +++ b/src/libtego/source/orconfig.h @@ -0,0 +1,25 @@ +#ifndef ORCONFIG_H +#define ORCONFIG_H + +// This header provides needed #defines normally stored in the orconfig.h header +// generatd by the tor build process + +/* Define to 1 iff memset(0) sets pointers to NULL */ +#define NULL_REP_IS_ZERO_BYTES 1 + +/* Define to 1 iff memset(0) sets doubles to 0.0 */ +#define DOUBLE_0_REP_IS_ZERO_BYTES 1 + +/* Define to 1 if the system has the type `ssize_t'. */ +#define HAVE_SSIZE_T 1 + +/* Define to 1 iff we represent negative integers with two's complement */ +#define USING_TWOS_COMPLEMENT 1 + +/* The size of `void *', as computed by sizeof. */ +#define SIZEOF_VOID_P 8 + +/* Define to 1 if you have the `EVP_sha3_256' function. */ +#define HAVE_EVP_SHA3_256 1 + +#endif // ORCONFIG_H \ No newline at end of file diff --git a/src/libtego/source/precomp.h b/src/libtego/source/precomp.h new file mode 100644 index 00000000..5a7f45c7 --- /dev/null +++ b/src/libtego/source/precomp.h @@ -0,0 +1,140 @@ +#include + +// C headers + +// os +#ifdef Q_OS_WIN +#define WIN32_LEAN_AND_MEAN +#include +#include +// workaround because protobuffer defines a GetMessage function +#undef GetMessage +#endif + +// standard library +#include + +// openssl +#include +#include +#include +#include +#include +#include + +// tor +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus +# include +# ifdef HAVE_EVP_SHA3_256 +# define OPENSSL_HAS_SHA3 +# endif // HAVE_EVP_SHA3_256 +# define ALL_BUGS_ARE_FATAL +# include +# include +# include +# include +# include +#ifdef __cplusplus +} + +// include our public header +#include + +#endif // __cplusplus + +// C++ headers +#ifdef __cplusplus + +// standard library +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// fmt +#include +#include + +// Qt +#include +#include + +// libtego_ui Qt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace tego +{ + template + constexpr size_t countof(T (&)[N]) + { + return N; + } +} + +#endif //__cplusplus#i diff --git a/src/protocol/AuthHiddenService.proto b/src/libtego/source/protocol/AuthHiddenService.proto similarity index 75% rename from src/protocol/AuthHiddenService.proto rename to src/libtego/source/protocol/AuthHiddenService.proto index afbe2093..775aa8f0 100644 --- a/src/protocol/AuthHiddenService.proto +++ b/src/libtego/source/protocol/AuthHiddenService.proto @@ -1,3 +1,5 @@ +syntax = "proto2"; + package Protocol.Data.AuthHiddenService; import "ControlChannel.proto"; @@ -15,8 +17,8 @@ message Packet { } message Proof { - optional bytes public_key = 1; // DER encoded public key - optional bytes signature = 2; // RSA signature + optional bytes signature = 1; // ED25519-V3 signature + optional string service_id = 2; // sans .onion prefix } message Result { diff --git a/src/protocol/AuthHiddenServiceChannel.cpp b/src/libtego/source/protocol/AuthHiddenServiceChannel.cpp similarity index 65% rename from src/protocol/AuthHiddenServiceChannel.cpp rename to src/libtego/source/protocol/AuthHiddenServiceChannel.cpp index 350c1ad7..0d2ae7bb 100644 --- a/src/protocol/AuthHiddenServiceChannel.cpp +++ b/src/libtego/source/protocol/AuthHiddenServiceChannel.cpp @@ -37,12 +37,15 @@ #include "utils/SecureRNG.h" #include "utils/CryptoKey.h" #include "utils/Useful.h" -#include +#include "utils/StringUtil.h" +#include "error.hpp" using namespace Protocol; namespace Protocol { +constexpr size_t COOKIE_SIZE = 16; + class AuthHiddenServiceChannelPrivate : public ChannelPrivate { public: @@ -50,13 +53,16 @@ class AuthHiddenServiceChannelPrivate : public ChannelPrivate QByteArray clientCookie, serverCookie; bool accepted; - AuthHiddenServiceChannelPrivate(Channel *q, Channel::Direction direction, Connection *conn) - : ChannelPrivate(q, QStringLiteral("im.ricochet.auth.hidden-service"), direction, conn) + AuthHiddenServiceChannelPrivate(Channel *q, Channel::Direction dir, Connection *conn) + : ChannelPrivate(q, QStringLiteral("im.ricochet.auth.hidden-service"), dir, conn) , accepted(false) { } - QByteArray getProofData(const QString &clientHostname); + QByteArray getProofData(const QByteArray& clientServiceId) const; +private: + QByteArray getProofKey() const; + QByteArray getProofMessage(const QByteArray &clientServiceId) const; }; } @@ -82,12 +88,12 @@ void AuthHiddenServiceChannel::setPrivateKey(const CryptoKey &key) { Q_D(AuthHiddenServiceChannel); if (isOpened()) { - BUG() << "Channel is already open"; + TEGO_BUG() << "Channel is already open"; return; } if (!key.isLoaded() || !key.isPrivate()) { - BUG() << "AuthHiddenServiceChannel cannot authenticate without a valid private key"; + TEGO_BUG() << "AuthHiddenServiceChannel cannot authenticate without a valid private key"; return; } @@ -123,21 +129,21 @@ bool AuthHiddenServiceChannel::allowInboundChannelRequest(const Data::Control::O // Store client cookie std::string clientCookie = request->GetExtension(Data::AuthHiddenService::client_cookie); - if (clientCookie.size() != 16) { + if (clientCookie.size() != COOKIE_SIZE) { qDebug() << "Received OpenChannel for" << type() << "with no valid client_cookie"; result->set_common_error(ChannelResult::BadUsageError); return false; } - d->clientCookie = QByteArray(clientCookie.c_str(), clientCookie.size()); + d->clientCookie = QByteArray(clientCookie.c_str(), safe_cast(clientCookie.size())); // Generate a random cookie and return result - d->serverCookie = SecureRNG::random(16); + d->serverCookie = SecureRNG::random(COOKIE_SIZE); if (d->serverCookie.isEmpty()) return false; qDebug() << "Accepted inbound AuthHiddenServiceChannel"; - result->SetExtension(Data::AuthHiddenService::server_cookie, std::string(d->serverCookie.constData(), d->serverCookie.size())); + result->SetExtension(Data::AuthHiddenService::server_cookie, std::string(d->serverCookie.constData(), static_cast(d->serverCookie.size()))); return true; } @@ -146,14 +152,14 @@ bool AuthHiddenServiceChannel::allowOutboundChannelRequest(Data::Control::OpenCh Q_D(AuthHiddenServiceChannel); if (!d->privateKey.isLoaded()) { - BUG() << "AuthHiddenServiceChannel can't be opened without a private key"; + TEGO_BUG() << "AuthHiddenServiceChannel can't be opened without a private key"; return false; } - d->clientCookie = SecureRNG::random(16); + d->clientCookie = SecureRNG::random(COOKIE_SIZE); if (d->clientCookie.isEmpty()) return false; - request->SetExtension(Data::AuthHiddenService::client_cookie, std::string(d->clientCookie.constData(), d->clientCookie.size())); + request->SetExtension(Data::AuthHiddenService::client_cookie, std::string(d->clientCookie.constData(), static_cast(d->clientCookie.size()))); return true; } @@ -163,12 +169,13 @@ bool AuthHiddenServiceChannel::processChannelOpenResult(const Data::Control::Cha if (result->opened()) { std::string cookie = result->GetExtension(Data::AuthHiddenService::server_cookie); - if (cookie.size() != 16) { + if (cookie.size() != COOKIE_SIZE) { qDebug() << "Received ChannelResult for" << type() << "with no valid server_cookie"; return false; } - - d->serverCookie = QByteArray(cookie.c_str(), cookie.size()); + + TEGO_THROW_IF_FALSE(cookie.size() <= std::numeric_limits::max()); + d->serverCookie = QByteArray(cookie.c_str(), static_cast(cookie.size())); return true; } @@ -180,43 +187,26 @@ void AuthHiddenServiceChannel::sendAuthMessage() Q_D(AuthHiddenServiceChannel); if (direction() != Outbound) { - BUG() << "Proof message is only sent from outbound channels"; + TEGO_BUG() << "Proof message is only sent from outbound channels"; return; } if (!isOpened()) return; - if (d->clientCookie.size() != 16 || d->serverCookie.size() != 16) { - BUG() << "AuthHiddenServiceChannel can't create a proof without valid cookies"; + if (d->clientCookie.size() != COOKIE_SIZE || d->serverCookie.size() != COOKIE_SIZE) { + TEGO_BUG() << "AuthHiddenServiceChannel can't create a proof without valid cookies"; closeChannel(); return; } - QByteArray publicKey = d->privateKey.encodedPublicKey(CryptoKey::DER); - if (publicKey.size() > 150) { - BUG() << "Unexpected size for encoded public key"; - closeChannel(); - return; - } - - QByteArray signature; QByteArray proofData = d->getProofData(d->privateKey.torServiceID()); - if (!proofData.isEmpty()) { - QByteArray proofHMAC = QMessageAuthenticationCode::hash(proofData, d->clientCookie + d->serverCookie, - QCryptographicHash::Sha256); - signature = d->privateKey.signSHA256(proofHMAC); - } - - if (signature.isEmpty()) { - BUG() << "Creating proof on AuthHiddenServiceChannel failed"; - closeChannel(); - return; - } + auto signature = d->privateKey.signData(proofData); QScopedPointer proof(new Data::AuthHiddenService::Proof); - proof->set_public_key(std::string(publicKey.constData(), publicKey.size())); - proof->set_signature(std::string(signature.constData(), signature.size())); + proof->set_signature(std::string(signature.constData(), static_cast(signature.size()))); + + proof->set_service_id(d->privateKey.torServiceID().toStdString()); Data::AuthHiddenService::Packet message; message.set_allocated_proof(proof.take()); @@ -225,17 +215,44 @@ void AuthHiddenServiceChannel::sendAuthMessage() qDebug() << "AuthHiddenServiceChannel sent outbound authentication packet"; } -QByteArray AuthHiddenServiceChannelPrivate::getProofData(const QString &client) +QByteArray AuthHiddenServiceChannelPrivate::getProofData(const QByteArray& clientServiceId) const { - QByteArray serverHostname = connection->serverHostname().toLatin1().mid(0, 16); - QByteArray clientHostname = client.toLatin1(); + auto proofMessage = this->getProofMessage(clientServiceId); + auto proofKey = this->getProofKey(); - if (clientHostname.size() != 16 || serverHostname.size() != 16) { - BUG() << "AuthHiddenServiceChannel can't figure out the client and server hostnames"; - return QByteArray(); + auto proofData = QMessageAuthenticationCode::hash(proofMessage, proofKey, QCryptographicHash::Sha256); + return proofData; + +} + +QByteArray AuthHiddenServiceChannelPrivate::getProofKey() const +{ + if (clientCookie.size() != COOKIE_SIZE) + { + TEGO_BUG() << "Invalid client cookie size; should be " << COOKIE_SIZE; + return {}; + } + if (serverCookie.size() != COOKIE_SIZE) + { + TEGO_BUG() << "Invalid server cookie size; should be " << COOKIE_SIZE; + return {}; } - return clientHostname + serverHostname; + return this->clientCookie + this->serverCookie; +} + +QByteArray AuthHiddenServiceChannelPrivate::getProofMessage(const QByteArray& clientServiceId) const +{ + // TODO: prepenend string 'ricochet-refresh proof' to the message to prevent hash reuse + QByteArray serverServiceId = connection->serverServiceId(); + + if (clientServiceId.size() != TEGO_V3_ONION_SERVICE_ID_LENGTH || serverServiceId.size() != TEGO_V3_ONION_SERVICE_ID_LENGTH) { + TEGO_BUG() << "AuthHiddenServiceChannel can't figure out the client and server hostnames"; + return {}; + } + + auto proofMessage = clientServiceId + serverServiceId; + return proofMessage; } void AuthHiddenServiceChannel::receivePacket(const QByteArray &packet) @@ -266,51 +283,55 @@ void AuthHiddenServiceChannel::handleProof(const Data::AuthHiddenService::Proof return; } - if (d->clientCookie.size() != 16 || d->serverCookie.size() != 16) { - BUG() << "AuthHiddenServiceChannel can't create a proof without valid cookies"; + if (d->clientCookie.size() != COOKIE_SIZE || d->serverCookie.size() != COOKIE_SIZE) { + TEGO_BUG() << "AuthHiddenServiceChannel can't create a proof without valid cookies"; closeChannel(); return; } - QByteArray publicKeyData(message.public_key().c_str(), message.public_key().size()); - QByteArray signature(message.signature().c_str(), message.signature().size()); + TEGO_THROW_IF_FALSE(message.signature().size() <= std::numeric_limits::max()); + TEGO_THROW_IF_FALSE(message.service_id().size() <= std::numeric_limits::max()); + QByteArray signature(message.signature().c_str(), static_cast(message.signature().size())); + QByteArray serviceId(message.service_id().c_str(), static_cast(message.service_id().size())); QScopedPointer result(new Data::AuthHiddenService::Result); + result->set_accepted(false); - // Hidden services always use a 1024bit key. A valid signature will always be exactly 128 bytes. - CryptoKey publicKey; - if (signature.size() != 128) { - qWarning() << "Received invalid signature (size" << signature.size() << ") on" << type(); - } else if (publicKeyData.size() > 150) { - qWarning() << "Received invalid public key (size" << publicKeyData.size() << ") on" << type(); - } else if (!publicKey.loadFromData(publicKeyData, CryptoKey::PublicKey, CryptoKey::DER)) { - qWarning() << "Unable to parse public key from" << type(); - } else if (publicKey.bits() != 1024) { - qWarning() << "Received invalid public key (" << publicKey.bits() << "bits) on" << type(); - } else { - bool ok = false; - QByteArray proofData = d->getProofData(publicKey.torServiceID()); - if (!proofData.isEmpty()) { - QByteArray proofHMAC = QMessageAuthenticationCode::hash(proofData, d->clientCookie + d->serverCookie, - QCryptographicHash::Sha256); - ok = publicKey.verifySHA256(proofHMAC, signature); + if (signature.size() == TEGO_ED25519_SIGNATURE_SIZE) + { + CryptoKey publicKey; + if(publicKey.loadFromServiceId(serviceId)) + { + auto proofData = d->getProofData(serviceId); + if (publicKey.verifyData(proofData, signature)) + { + result->set_accepted(true); + } + else + { + qWarning() << "Signature verification failed on" << type(); + } } - - if (!ok) { - qWarning() << "Signature verification failed on" << type(); - result->set_accepted(false); - } else { - result->set_accepted(true); - qDebug() << type() << "accepted inbound authentication for" << publicKey.torServiceID(); + else + { + qWarning() << "Unable to parse public key from" << type(); } } + else + { + qWarning() << "Received Signature with incorrect size from" << type(); + } - if (result->accepted()) { - connection()->grantAuthentication(Connection::HiddenServiceAuth, publicKey.torServiceID() + QStringLiteral(".onion")); + if (result->accepted()) + { + // TODO: send back our own signature with our private key for server to verify + const auto hostname = serviceId + ".onion"; + connection()->grantAuthentication(Connection::HiddenServiceAuth, hostname); d->accepted = true; result->set_is_known_contact(connection()->purpose() == Connection::Purpose::KnownContact); - } else { + } else + { d->accepted = false; } diff --git a/src/protocol/AuthHiddenServiceChannel.h b/src/libtego/source/protocol/AuthHiddenServiceChannel.h similarity index 100% rename from src/protocol/AuthHiddenServiceChannel.h rename to src/libtego/source/protocol/AuthHiddenServiceChannel.h diff --git a/src/protocol/Channel.cpp b/src/libtego/source/protocol/Channel.cpp similarity index 84% rename from src/protocol/Channel.cpp rename to src/libtego/source/protocol/Channel.cpp index 891c73a6..a31f5e74 100644 --- a/src/protocol/Channel.cpp +++ b/src/libtego/source/protocol/Channel.cpp @@ -34,11 +34,10 @@ #include "Connection_p.h" #include "ControlChannel.h" #include "utils/Useful.h" -#include - #include "AuthHiddenServiceChannel.h" #include "ChatChannel.h" #include "ContactRequestChannel.h" +#include "FileChannel.h" using namespace Protocol; @@ -53,6 +52,8 @@ Channel *Channel::create(const QString &type, Direction direction, Connection *c return new ChatChannel(direction, connection); } else if (type == QStringLiteral("im.ricochet.contact.request")) { return new ContactRequestChannel(direction, connection); + } else if (type == QStringLiteral("im.ricochet.file-transfer")) { + return new FileChannel(direction, connection); } else { return 0; } @@ -64,9 +65,9 @@ Channel::Channel(const QString &type, Direction direction, Connection *connectio { } -Channel::Channel(ChannelPrivate *d_ptr) - : QObject(d_ptr->connection) - , d_ptr(d_ptr) +Channel::Channel(ChannelPrivate *d) + : QObject(d->connection) + , d_ptr(d) { } @@ -108,7 +109,7 @@ bool Channel::isOpened() const { Q_D(const Channel); if (d->isOpened && d->identifier < 0) - BUG() << "Channel is marked as open, but has no identifier"; + TEGO_BUG() << "Channel is marked as open, but has no identifier"; return d->isOpened; } @@ -116,14 +117,14 @@ bool Channel::openChannel() { Q_D(Channel); if (direction() != Channel::Outbound || isOpened() || identifier() >= 0) { - BUG() << "Cannot send request to open" << type() << "channel in an incorrect state"; + TEGO_BUG() << "Cannot send request to open" << type() << "channel in an incorrect state"; if (isOpened()) closeChannel(); d->invalidate(); return false; } else if (!connection()->findChannel()->sendOpenChannel(this)) { if (isOpened()) { - BUG() << "Channel somehow opened instantly in an impossible situation"; + TEGO_BUG() << "Channel somehow opened instantly in an impossible situation"; closeChannel(); } d->invalidate(); @@ -158,12 +159,12 @@ bool ChannelPrivate::openChannelInbound(const Data::Control::OpenChannel *reques Q_Q(Channel); result->set_opened(false); if (direction != Channel::Inbound || isOpened || identifier >= 0 || hasSentClose || isInvalidated) { - BUG() << "Handling inbound open channel request on a channel in an unexpected state; rejecting"; + TEGO_BUG() << "Handling inbound open channel request on a channel in an unexpected state; rejecting"; return false; } if (request->channel_identifier() <= 0) { - BUG() << "Invalid channel identifier in inboundOpenChannel handler"; + TEGO_BUG() << "Invalid channel identifier in inboundOpenChannel handler"; return false; } @@ -184,7 +185,7 @@ bool ChannelPrivate::openChannelInbound(const Data::Control::OpenChannel *reques } if (result->has_common_error()) { - BUG() << "Accepted inbound OpenChannel request, but result has error details set. Assuming it's actually an error."; + TEGO_BUG() << "Accepted inbound OpenChannel request, but result has error details set. Assuming it's actually an error."; result->set_opened(false); return false; } @@ -200,7 +201,7 @@ bool ChannelPrivate::openChannelOutbound(Data::Control::OpenChannel *request) { Q_Q(Channel); if (direction != Channel::Outbound || isOpened || identifier >= 0) { - BUG() << "Handling outbound open channel request on a channel in an unexpected state; rejecting"; + TEGO_BUG() << "Handling outbound open channel request on a channel in an unexpected state; rejecting"; return false; } @@ -222,7 +223,7 @@ bool ChannelPrivate::openChannelResult(const Data::Control::ChannelResult *resul Q_Q(Channel); // ControlChannel should weed out clearly invalid messages, so assert here if it didn't if (direction != Channel::Outbound || isOpened || identifier < 0) { - BUG() << "Handling response for outbound open channel on a channel in an unexpected state; ignoring"; + TEGO_BUG() << "Handling response for outbound open channel on a channel in an unexpected state; ignoring"; return false; } @@ -258,17 +259,17 @@ bool Channel::sendPacket(const QByteArray &packet) { Q_D(Channel); if (d->identifier < 0) { - BUG() << "Cannot send packet to channel" << type() << "without an assigned identifier"; + TEGO_BUG() << "Cannot send packet to channel" << type() << "without an assigned identifier"; return false; } if (packet.size() == 0) { - BUG() << "Cannot send empty packet to channel" << type(); + TEGO_BUG() << "Cannot send empty packet to channel" << type(); return false; } if (packet.size() > ConnectionPrivate::PacketMaxDataSize) { - BUG() << "Packet is too big on channel" << type(); + TEGO_BUG() << "Packet is too big on channel" << type(); return false; } @@ -278,19 +279,19 @@ bool Channel::sendPacket(const QByteArray &packet) void Channel::requestInboundApproval() { if (direction() != Channel::Inbound || isOpened()) { - BUG() << "Called in an unexpected channel state"; + TEGO_BUG() << "Called in an unexpected channel state"; return; } emit connection()->channelRequestingInboundApproval(this); } -ChannelPrivate::ChannelPrivate(Channel *q, const QString &type, Channel::Direction direction, Connection *conn) +ChannelPrivate::ChannelPrivate(Channel *q, const QString &c_type, Channel::Direction dir, Connection *conn) : q_ptr(q) , connection(conn) - , type(type) + , type(c_type) , identifier(-1) - , direction(direction) + , direction(dir) , isOpened(false) , hasSentClose(false) , isInvalidated(false) @@ -301,7 +302,7 @@ ChannelPrivate::~ChannelPrivate() { Q_Q(Channel); if (identifier >= 0 && !isInvalidated) { - BUG() << "Channel of type" << type << "was deleted without being invalidated"; + TEGO_BUG() << "Channel of type" << type << "was deleted without being invalidated"; connection->d->removeChannel(q); } } diff --git a/src/protocol/Channel.h b/src/libtego/source/protocol/Channel.h similarity index 99% rename from src/protocol/Channel.h rename to src/libtego/source/protocol/Channel.h index e6b13f89..2adbe23e 100644 --- a/src/protocol/Channel.h +++ b/src/libtego/source/protocol/Channel.h @@ -33,8 +33,6 @@ #ifndef PROTOCOL_CHANNEL_H #define PROTOCOL_CHANNEL_H -#include -#include #include "ControlChannel.pb.h" namespace Protocol diff --git a/src/protocol/Channel_p.h b/src/libtego/source/protocol/Channel_p.h similarity index 86% rename from src/protocol/Channel_p.h rename to src/libtego/source/protocol/Channel_p.h index 51783847..e053b296 100644 --- a/src/protocol/Channel_p.h +++ b/src/libtego/source/protocol/Channel_p.h @@ -36,7 +36,6 @@ #include "Channel.h" #include "Connection_p.h" #include "utils/Useful.h" -#include namespace Protocol { @@ -72,14 +71,14 @@ template bool Channel::sendMessage(const T &message) { int size = message.ByteSize(); if (size > ConnectionPrivate::PacketMaxDataSize) { - BUG() << "Message on" << type() << "channel is too big -" << size << "bytes:" - << QString::fromStdString(message.DebugString()); + TEGO_BUG() << "Message on" << type() << "channel is too big -" << size << "bytes:" + << QString::fromStdString(message.DebugString()); return false; } if (size < 1) { - BUG() << "Message on" << type() << "channel encoded as invalid length; this isn't possible to send:" - << QString::fromStdString(message.DebugString()); + TEGO_BUG() << "Message on" << type() << "channel encoded as invalid length; this isn't possible to send:" + << QString::fromStdString(message.DebugString()); return false; } @@ -87,7 +86,7 @@ template bool Channel::sendMessage(const T &message) quint8 *end = message.SerializeWithCachedSizesToArray(reinterpret_cast(packet.data())); quint8 *expected_end = reinterpret_cast(packet.data() + size); if (end != expected_end) { - BUG() << "Unexpected packet size after message serialization. Expected" << size << "but got" << qptrdiff(end - expected_end); + TEGO_BUG() << "Unexpected packet size after message serialization. Expected" << size << "but got" << qptrdiff(end - expected_end); return false; } diff --git a/src/protocol/ChatChannel.cpp b/src/libtego/source/protocol/ChatChannel.cpp similarity index 86% rename from src/protocol/ChatChannel.cpp rename to src/libtego/source/protocol/ChatChannel.cpp index 0e62b00e..5997ce0e 100644 --- a/src/protocol/ChatChannel.cpp +++ b/src/libtego/source/protocol/ChatChannel.cpp @@ -33,7 +33,6 @@ #include "ChatChannel.h" #include "Channel_p.h" #include "Connection.h" -#include "utils/SecureRNG.h" #include "utils/Useful.h" using namespace Protocol; @@ -41,9 +40,6 @@ using namespace Protocol; ChatChannel::ChatChannel(Direction direction, Connection *connection) : Channel(QStringLiteral("im.ricochet.chat"), direction, connection) { - // The peer might use recent message IDs between connections to handle - // re-send. Start at a random ID to reduce chance of collisions, then increment - lastMessageId = SecureRNG::randomInt(UINT32_MAX); } bool ChatChannel::allowInboundChannelRequest(const Data::Control::OpenChannel *request, Data::Control::ChannelResult *result) @@ -69,12 +65,12 @@ bool ChatChannel::allowOutboundChannelRequest(Data::Control::OpenChannel *reques Q_UNUSED(request); if (connection()->findChannel(Channel::Outbound)) { - BUG() << "Rejecting outbound request for" << type() << "channel because one is already open on this connection"; + TEGO_BUG() << "Rejecting outbound request for" << type() << "channel because one is already open on this connection"; return false; } if (connection()->purpose() != Connection::Purpose::KnownContact) { - BUG() << "Rejecting outbound request for" << type() << "channel for connection with unexpected purpose" << int(connection()->purpose()); + TEGO_BUG() << "Rejecting outbound request for" << type() << "channel for connection with unexpected purpose" << int(connection()->purpose()); return false; } @@ -99,16 +95,11 @@ void ChatChannel::receivePacket(const QByteArray &packet) } } -bool ChatChannel::sendChatMessage(QString text, QDateTime time, MessageId &id) -{ - id = ++lastMessageId; - return sendChatMessageWithId(text, time, id); -} bool ChatChannel::sendChatMessageWithId(QString text, QDateTime time, MessageId id) { if (direction() != Outbound) { - BUG() << "Chat channels are unidirectional, and this is not an outbound channel"; + TEGO_BUG() << "Chat channels are unidirectional, and this is not an outbound channel"; return false; } @@ -116,10 +107,10 @@ bool ChatChannel::sendChatMessageWithId(QString text, QDateTime time, MessageId message->set_message_id(id); if (text.isEmpty()) { - BUG() << "Chat message is empty, and it should've been discarded"; + TEGO_BUG() << "Chat message is empty, and it should've been discarded"; return false; } else if (text.size() > MessageMaxCharacters) { - BUG() << "Chat message is too long (" << text.size() << "characters), and it should've been limited already. Truncated."; + TEGO_BUG() << "Chat message is too long (" << text.size() << "characters), and it should've been limited already. Truncated."; text.truncate(MessageMaxCharacters); } diff --git a/src/protocol/ChatChannel.h b/src/libtego/source/protocol/ChatChannel.h similarity index 94% rename from src/protocol/ChatChannel.h rename to src/libtego/source/protocol/ChatChannel.h index d45cf66e..f7be7c4e 100644 --- a/src/protocol/ChatChannel.h +++ b/src/libtego/source/protocol/ChatChannel.h @@ -33,10 +33,8 @@ #ifndef PROTOCOL_CHATCHANNEL_H #define PROTOCOL_CHATCHANNEL_H -#include "Channel.h" +#include "protocol/Channel.h" #include "ChatChannel.pb.h" -#include -#include namespace Protocol { @@ -52,7 +50,6 @@ class ChatChannel : public Channel explicit ChatChannel(Direction direction, Connection *connection); - bool sendChatMessage(QString text, QDateTime time, MessageId &id); bool sendChatMessageWithId(QString text, QDateTime time, MessageId id); signals: @@ -66,7 +63,6 @@ class ChatChannel : public Channel private: QSet pendingMessages; - MessageId lastMessageId; void handleChatMessage(const Data::Chat::ChatMessage &message); void handleChatAcknowledge(const Data::Chat::ChatAcknowledge &message); diff --git a/src/protocol/ChatChannel.proto b/src/libtego/source/protocol/ChatChannel.proto similarity index 96% rename from src/protocol/ChatChannel.proto rename to src/libtego/source/protocol/ChatChannel.proto index 30adac46..3a52e5b7 100644 --- a/src/protocol/ChatChannel.proto +++ b/src/libtego/source/protocol/ChatChannel.proto @@ -1,3 +1,5 @@ +syntax = "proto2"; + package Protocol.Data.Chat; message Packet { diff --git a/src/protocol/Connection.cpp b/src/libtego/source/protocol/Connection.cpp similarity index 83% rename from src/protocol/Connection.cpp rename to src/libtego/source/protocol/Connection.cpp index 042c8582..97399ad4 100644 --- a/src/protocol/Connection.cpp +++ b/src/libtego/source/protocol/Connection.cpp @@ -33,10 +33,6 @@ #include "Connection_p.h" #include "ControlChannel.h" #include "utils/Useful.h" -#include -#include -#include -#include using namespace Protocol; @@ -125,22 +121,28 @@ QString Connection::serverHostname() const hostname = d->socket->property("localHostname").toString(); if (!hostname.endsWith(QStringLiteral(".onion"))) { - BUG() << "Connection does not have a valid server hostname:" << hostname; + TEGO_BUG() << "Connection does not have a valid server hostname:" << hostname; return QString(); } return hostname; } +QByteArray Connection::serverServiceId() const +{ + auto hostname = this->serverHostname(); + return hostname.toUtf8().left(TEGO_V3_ONION_SERVICE_ID_LENGTH); +} + int Connection::age() const { - return qRound(d->ageTimer.elapsed() / 1000.0); + return qRound(static_cast(d->ageTimer.elapsed()) / 1000.0); } void ConnectionPrivate::setSocket(QTcpSocket *s, Connection::Direction d) { if (socket) { - BUG() << "Connection already has a socket"; + TEGO_BUG() << "Connection already has a socket"; return; } @@ -152,7 +154,7 @@ void ConnectionPrivate::setSocket(QTcpSocket *s, Connection::Direction d) socket->setParent(q); if (socket->state() != QAbstractSocket::ConnectedState) { - BUG() << "Connection created with socket in a non-connected state" << socket->state(); + TEGO_BUG() << "Connection created with socket in a non-connected state" << socket->state(); } Channel *control = new ControlChannel(direction == Connection::ClientSide ? Channel::Outbound : Channel::Inbound, q); @@ -161,7 +163,7 @@ void ConnectionPrivate::setSocket(QTcpSocket *s, Connection::Direction d) insertChannel(control); if (!control->isOpened() || control->identifier() != 0 || q->channel(0) != control) { - BUG() << "Control channel on new connection is not set up properly"; + TEGO_BUG() << "Control channel on new connection is not set up properly"; q->close(); return; } @@ -170,7 +172,7 @@ void ConnectionPrivate::setSocket(QTcpSocket *s, Connection::Direction d) // The server side is implicitly authenticated (by the transport) as the correct service, so grant that QString serverName = q->serverHostname(); if (serverName.isEmpty()) { - BUG() << "Server side of connection doesn't have an authenticated name, aborting"; + TEGO_BUG() << "Server side of connection doesn't have an authenticated name, aborting"; q->close(); return; } @@ -178,8 +180,8 @@ void ConnectionPrivate::setSocket(QTcpSocket *s, Connection::Direction d) q->grantAuthentication(Connection::HiddenServiceAuth, serverName); // Send the introduction version handshake message - char intro[] = { 0x49, 0x4D, 0x02, ProtocolVersion, 0 }; - if (socket->write(intro, sizeof(intro)) < (int)sizeof(intro)) { + char intro[] = { 0x49, 0x4D, 0x01, ProtocolVersion }; + if (socket->write(intro, sizeof(intro)) < static_cast(sizeof(intro))) { qDebug() << "Failed writing introduction message to socket"; q->close(); return; @@ -208,7 +210,7 @@ void ConnectionPrivate::closeImmediately() socket->abort(); if (!wasClosed) { - BUG() << "Socket was forcefully closed but never emitted closed signal"; + TEGO_BUG() << "Socket was forcefully closed but never emitted closed signal"; wasClosed = true; emit q->closed(); } @@ -216,19 +218,20 @@ void ConnectionPrivate::closeImmediately() if (!channels.isEmpty()) { foreach (Channel *c, channels) qDebug() << "Open channel:" << c << c->type() << c->connection(); - BUG() << "Channels remain open after forcefully closing connection socket"; + TEGO_BUG() << "Channels remain open after forcefully closing connection socket"; } } void ConnectionPrivate::socketDisconnected() { qDebug() << "Connection" << this << "disconnected"; - closeAllChannels(); - + // emit close signal first so FileChannel can bubble up errors if (!wasClosed) { wasClosed = true; emit q->closed(); } + + closeAllChannels(); } void ConnectionPrivate::socketReadable() @@ -262,7 +265,7 @@ void ConnectionPrivate::socketReadable() // Expecting at least 3 bytes uchar intro[3] = { 0 }; qint64 re = socket->peek(reinterpret_cast(intro), sizeof(intro)); - if (re < (int)sizeof(intro)) { + if (re < static_cast(sizeof(intro))) { qDebug() << "Connection socket error" << socket->error() << "during read:" << socket->errorString(); socket->abort(); return; @@ -275,11 +278,12 @@ void ConnectionPrivate::socketReadable() return; } - if (available < (qint64)sizeof(intro) + nVersions) + if (available < static_cast(sizeof(intro)) + nVersions) return; // Discard intro header re = socket->read(reinterpret_cast(intro), sizeof(intro)); + (void)re; QByteArray versions(nVersions, 0); re = socket->read(versions.data(), versions.size()); @@ -290,9 +294,9 @@ void ConnectionPrivate::socketReadable() } quint8 selectedVersion = ProtocolVersionFailed; - foreach (quint8 v, versions) { - if (v == ProtocolVersion) { - selectedVersion = v; + for (auto v : versions) { + if (static_cast(v) == ProtocolVersion) { + selectedVersion = static_cast(v); break; } } @@ -329,7 +333,7 @@ void ConnectionPrivate::socketReadable() socket->abort(); return; } else if (re < PacketHeaderSize) { - BUG() << "Socket had" << available << "bytes available but peek only returned" << re; + TEGO_BUG() << "Socket had" << available << "bytes available but peek only returned" << re; return; } @@ -354,7 +358,7 @@ void ConnectionPrivate::socketReadable() } else { // Because of QTcpSocket buffering, we can expect that up to 'available' bytes // will read. Treat anything less as an error condition. - BUG() << "Socket read was unexpectedly small;" << available << "bytes should've been available but we read" << re; + TEGO_BUG() << "Socket read was unexpectedly small;" << available << "bytes should've been available but we read" << re; } socket->abort(); return; @@ -368,7 +372,7 @@ void ConnectionPrivate::socketReadable() qDebug() << "Connection socket error" << socket->error() << "during read:" << socket->errorString(); } else { // As above - BUG() << "Socket read was unexpectedly small;" << available << "bytes should've been available but we read" << re; + TEGO_BUG() << "Socket read was unexpectedly small;" << available << "bytes should've been available but we read" << re; } socket->abort(); return; @@ -390,7 +394,7 @@ void ConnectionPrivate::socketReadable() if (channel->connection() != q) { // If this fails, something is extremely broken. It may be dangerous to continue // processing any data at all. Crash gracefully. - BUG() << "Channel" << channelId << "found on connection" << this << "but its connection is" + TEGO_BUG() << "Channel" << channelId << "found on connection" << this << "but its connection is" << channel->connection(); qFatal("Connection mismatch while handling packet"); return; @@ -408,7 +412,7 @@ bool ConnectionPrivate::writePacket(Channel *channel, const QByteArray &data) { if (channel->connection() != q) { // As above, dangerously broken, crash the process to avoid damage - BUG() << "Writing packet for channel" << channel->identifier() << "on connection" << this + TEGO_BUG() << "Writing packet for channel" << channel->identifier() << "on connection" << this << "but its connection is" << channel->connection(); qFatal("Connection mismatch while writing packet"); return false; @@ -420,12 +424,12 @@ bool ConnectionPrivate::writePacket(Channel *channel, const QByteArray &data) bool ConnectionPrivate::writePacket(int channelId, const QByteArray &data) { if (channelId < 0 || channelId > UINT16_MAX) { - BUG() << "Cannot write packet for channel with invalid identifier" << channelId; + TEGO_BUG() << "Cannot write packet for channel with invalid identifier" << channelId; return false; } if (data.size() > PacketMaxDataSize) { - BUG() << "Cannot write oversized packet of" << data.size() << "bytes to channel" << channelId; + TEGO_BUG() << "Cannot write oversized packet of" << data.size() << "bytes to channel" << channelId; return false; } @@ -447,7 +451,7 @@ bool ConnectionPrivate::writePacket(int channelId, const QByteArray &data) return false; } - re = socket->write(data); + re = socket->write(data, data.size()); if (re != data.size()) { qDebug() << "Connection socket error" << socket->error() << "during write:" << socket->errorString(); socket->abort(); @@ -469,7 +473,7 @@ int ConnectionPrivate::availableOutboundChannelId() // Find an unused id, trying a maximum of 100 times, using a random step to avoid collision for (int i = 0; i < 100 && channels.contains(nextOutboundChannelId); i++) { - nextOutboundChannelId += 1 + (qrand() % 200); + nextOutboundChannelId += 1 + (QRandomGenerator::global()->bounded(200)); if (evenNumbered) nextOutboundChannelId += nextOutboundChannelId % 2; if (nextOutboundChannelId > maxId) @@ -478,18 +482,18 @@ int ConnectionPrivate::availableOutboundChannelId() if (channels.contains(nextOutboundChannelId)) { // Abort the connection if we still couldn't find an id, because it's probably a nasty bug - BUG() << "Can't find an available outbound channel ID for connection; aborting connection"; + TEGO_BUG() << "Can't find an available outbound channel ID for connection; aborting connection"; socket->abort(); return -1; } if (nextOutboundChannelId < minId || nextOutboundChannelId > maxId) { - BUG() << "Selected a channel id that isn't within range"; + TEGO_BUG() << "Selected a channel id that isn't within range"; return -1; } if (evenNumbered == bool(nextOutboundChannelId % 2)) { - BUG() << "Selected a channel id that isn't valid for this side of the connection"; + TEGO_BUG() << "Selected a channel id that isn't valid for this side of the connection"; return -1; } @@ -516,23 +520,23 @@ bool ConnectionPrivate::isValidAvailableChannelId(int id, Connection::Direction bool ConnectionPrivate::insertChannel(Channel *channel) { if (channel->connection() != q) { - BUG() << "Connection tried to insert a channel assigned to a different connection"; + TEGO_BUG() << "Connection tried to insert a channel assigned to a different connection"; return false; } if (channel->identifier() < 0) { - BUG() << "Connection tried to insert a channel without a valid identifier"; + TEGO_BUG() << "Connection tried to insert a channel without a valid identifier"; return false; } if (channels.contains(channel->identifier())) { - BUG() << "Connection tried to insert a channel with a duplicate id" << channel->identifier() + TEGO_BUG() << "Connection tried to insert a channel with a duplicate id" << channel->identifier() << "- we have" << channels.value(channel->identifier()) << "and inserted" << channel; return false; } if (channel->parent() != q) { - BUG() << "Connection inserted a channel without expected parent object. Fixing."; + TEGO_BUG() << "Connection inserted a channel without expected parent object. Fixing."; channel->setParent(q); } @@ -543,7 +547,7 @@ bool ConnectionPrivate::insertChannel(Channel *channel) void ConnectionPrivate::removeChannel(Channel *channel) { if (channel->connection() != q) { - BUG() << "Connection tried to remove a channel assigned to a different connection"; + TEGO_BUG() << "Connection tried to remove a channel assigned to a different connection"; return; } @@ -564,7 +568,7 @@ void ConnectionPrivate::closeAllChannels() channel->closeChannel(); if (!channels.isEmpty()) - BUG() << "Channels remain open on connection after calling closeAllChannels"; + TEGO_BUG() << "Channels remain open on connection after calling closeAllChannels"; } QHash Connection::channels() @@ -589,34 +593,34 @@ bool Connection::setPurpose(Purpose value) switch (value) { case Purpose::Unknown: - BUG() << "A connection can't reset to unknown purpose"; + TEGO_BUG() << "A connection can't reset to unknown purpose"; return false; case Purpose::KnownContact: if (!hasAuthenticated(HiddenServiceAuth)) { - BUG() << "Connection purpose cannot be KnownContact without authenticating a service"; + TEGO_BUG() << "Connection purpose cannot be KnownContact without authenticating a service"; return false; } break; case Purpose::OutboundRequest: if (d->direction != ClientSide) { - BUG() << "Connection purpose cannot be OutboundRequest on an inbound connection"; + TEGO_BUG() << "Connection purpose cannot be OutboundRequest on an inbound connection"; return false; } else if (d->purpose != Purpose::Unknown) { - BUG() << "Connection purpose cannot change from" << int(d->purpose) << "to OutboundRequest"; + TEGO_BUG() << "Connection purpose cannot change from" << int(d->purpose) << "to OutboundRequest"; return false; } break; case Purpose::InboundRequest: if (d->direction != ServerSide) { - BUG() << "Connection purpose cannot be InboundRequest on an outbound connection"; + TEGO_BUG() << "Connection purpose cannot be InboundRequest on an outbound connection"; return false; } else if (d->purpose != Purpose::Unknown) { - BUG() << "Connection purpose cannot change from" << int(d->purpose) << "to InboundRequest"; + TEGO_BUG() << "Connection purpose cannot change from" << int(d->purpose) << "to InboundRequest"; return false; } break; default: - BUG() << "Purpose type" << int(value) << "is not defined"; + TEGO_BUG() << "Purpose type" << int(value) << "is not defined"; return false; } @@ -647,7 +651,7 @@ QString Connection::authenticatedIdentity(AuthenticationType type) const void Connection::grantAuthentication(AuthenticationType type, const QString &identity) { if (hasAuthenticated(type)) { - BUG() << "Tried to redundantly grant" << type << "authentication to connection"; + TEGO_BUG() << "Tried to redundantly grant" << type << "authentication to connection"; return; } diff --git a/src/protocol/Connection.h b/src/libtego/source/protocol/Connection.h similarity index 96% rename from src/protocol/Connection.h rename to src/libtego/source/protocol/Connection.h index e29f1ac8..05d93412 100644 --- a/src/protocol/Connection.h +++ b/src/libtego/source/protocol/Connection.h @@ -33,9 +33,7 @@ #ifndef PROTOCOL_CONNECTION_H #define PROTOCOL_CONNECTION_H -#include -#include -#include "Channel.h" +#include "protocol/Channel.h" class QTcpSocket; @@ -109,6 +107,14 @@ class Connection : public QObject */ QString serverHostname() const; + /* Service Id of the server side of the connection + * + * Follows same rules as serverHostname(), but returns just the + * serviceId portion of the hostname (so ths does not end with + * ".onion") + */ + QByteArray serverServiceId() const; + /* Age of the connection in seconds */ int age() const; diff --git a/src/protocol/Connection_p.h b/src/libtego/source/protocol/Connection_p.h similarity index 96% rename from src/protocol/Connection_p.h rename to src/libtego/source/protocol/Connection_p.h index cdaad9cd..2f1893f6 100644 --- a/src/protocol/Connection_p.h +++ b/src/libtego/source/protocol/Connection_p.h @@ -34,9 +34,6 @@ #define PROTOCOL_CONNECTION_P_H #include "Connection.h" -#include -#include -#include namespace Protocol { @@ -47,7 +44,7 @@ class ConnectionPrivate : public QObject Q_DISABLE_COPY(ConnectionPrivate) public: - static const quint8 ProtocolVersion = 1; + static const quint8 ProtocolVersion = 3; static const quint8 ProtocolVersionFailed = 0xff; static const int PacketHeaderSize = 4; static const int PacketMaxDataSize = UINT16_MAX - PacketHeaderSize; diff --git a/src/protocol/ContactRequestChannel.cpp b/src/libtego/source/protocol/ContactRequestChannel.cpp similarity index 90% rename from src/protocol/ContactRequestChannel.cpp rename to src/libtego/source/protocol/ContactRequestChannel.cpp index 8d4da5dd..8a547ec3 100644 --- a/src/protocol/ContactRequestChannel.cpp +++ b/src/libtego/source/protocol/ContactRequestChannel.cpp @@ -62,18 +62,18 @@ QString ContactRequestChannel::message() const void ContactRequestChannel::setMessage(const QString &message) { if (direction() != Outbound) { - BUG() << "Request messages can only be set on outbound messages"; + TEGO_BUG() << "Request messages can only be set on outbound messages"; return; } // Only valid before channel opened if (isOpened() || identifier() >= 0) { - BUG() << "Request data must be set before opening channel"; + TEGO_BUG() << "Request data must be set before opening channel"; return; } if (message.size() > Data::ContactRequest::MessageMaxCharacters) { - BUG() << "Outbound contact request message is too long (" << message.size() << ")"; + TEGO_BUG() << "Outbound contact request message is too long (" << message.size() << ")"; return; } @@ -110,17 +110,17 @@ QString ContactRequestChannel::nickname() const void ContactRequestChannel::setNickname(const QString &nickname) { if (direction() != Outbound) { - BUG() << "Request messages can only be set on outbound messages"; + TEGO_BUG() << "Request messages can only be set on outbound messages"; return; } if (isOpened() || identifier() >= 0) { - BUG() << "Request data must be set before opening channel"; + TEGO_BUG() << "Request data must be set before opening channel"; return; } if (!isAcceptableNickname(nickname)) { - BUG() << "Outbound contact request nickname isn't acceptable:" << nickname; + TEGO_BUG() << "Outbound contact request nickname isn't acceptable:" << nickname; return; } @@ -181,7 +181,7 @@ bool ContactRequestChannel::allowInboundChannelRequest(const Data::Control::Open emit requestReceived(); if (m_responseStatus == Response::Undefined) { - BUG() << "No response to incoming contact request after requestReceived signal"; + TEGO_BUG() << "No response to incoming contact request after requestReceived signal"; setResponseStatus(Response::Error); } } @@ -202,13 +202,13 @@ void ContactRequestChannel::setResponseStatus(Status status) return; if (direction() != Inbound) { - BUG() << "Can't set the response on an outbound contact request"; + TEGO_BUG() << "Can't set the response on an outbound contact request"; return; } using namespace Data::ContactRequest; if (m_responseStatus > Response::Pending) - BUG() << "Response status is already a final state" << m_responseStatus << "but was changed to" << status; + TEGO_BUG() << "Response status is already a final state" << m_responseStatus << "but was changed to" << status; m_responseStatus = status; @@ -228,13 +228,13 @@ bool ContactRequestChannel::allowOutboundChannelRequest(Data::Control::OpenChann if (connection()->direction() != Connection::ClientSide || connection()->purpose() != Connection::Purpose::OutboundRequest) { - BUG() << "ContactRequestChannel can only be used on OutboundRequest connections. Has purpose" + TEGO_BUG() << "ContactRequestChannel can only be used on OutboundRequest connections. Has purpose" << int(connection()->purpose()); return false; } if (connection()->findChannel()) { - BUG() << "ContactRequestChannel can only be used once per connection"; + TEGO_BUG() << "ContactRequestChannel can only be used once per connection"; return false; } diff --git a/src/protocol/ContactRequestChannel.h b/src/libtego/source/protocol/ContactRequestChannel.h similarity index 100% rename from src/protocol/ContactRequestChannel.h rename to src/libtego/source/protocol/ContactRequestChannel.h diff --git a/src/protocol/ContactRequestChannel.proto b/src/libtego/source/protocol/ContactRequestChannel.proto similarity index 97% rename from src/protocol/ContactRequestChannel.proto rename to src/libtego/source/protocol/ContactRequestChannel.proto index 75591d4a..a1a81c49 100644 --- a/src/protocol/ContactRequestChannel.proto +++ b/src/libtego/source/protocol/ContactRequestChannel.proto @@ -1,3 +1,5 @@ +syntax = "proto2"; + package Protocol.Data.ContactRequest; import "ControlChannel.proto"; diff --git a/src/protocol/ControlChannel.cpp b/src/libtego/source/protocol/ControlChannel.cpp similarity index 87% rename from src/protocol/ControlChannel.cpp rename to src/libtego/source/protocol/ControlChannel.cpp index 877ffdf4..0db571fa 100644 --- a/src/protocol/ControlChannel.cpp +++ b/src/libtego/source/protocol/ControlChannel.cpp @@ -34,8 +34,6 @@ #include "Channel_p.h" #include "Connection_p.h" #include "utils/Useful.h" -#include -#include using namespace Protocol; @@ -43,7 +41,7 @@ ControlChannel::ControlChannel(Direction direction, Connection *connection) : Channel(QStringLiteral("control"), direction, connection) { if (connection->channel(0)) - BUG() << "Created ControlChannel for connection which already has a channel 0"; + TEGO_BUG() << "Created ControlChannel for connection which already has a channel 0"; Q_D(Channel); d->isOpened = true; @@ -53,12 +51,12 @@ ControlChannel::ControlChannel(Direction direction, Connection *connection) bool ControlChannel::sendOpenChannel(Channel *channel) { if (channel->isOpened() || channel->direction() != Outbound || channel->identifier() >= 0) { - BUG() << "openChannel called for a" << channel->type() << "channel in an unexpected state"; + TEGO_BUG() << "openChannel called for a" << channel->type() << "channel in an unexpected state"; return false; } if (channel->connection() != connection()) { - BUG() << "openChannel called for" << channel->type() << "channel on a different connection"; + TEGO_BUG() << "openChannel called for" << channel->type() << "channel on a different connection"; return false; } @@ -76,17 +74,17 @@ bool ControlChannel::sendOpenChannel(Channel *channel) if (!request->has_channel_type() || !request->has_channel_identifier() || request->channel_identifier() < 0 || request->channel_identifier() > UINT16_MAX) { - BUG() << "Outbound OpenChannel request isn't valid:" << QString::fromStdString(request->DebugString()); + TEGO_BUG() << "Outbound OpenChannel request isn't valid:" << QString::fromStdString(request->DebugString()); return false; } if (request->channel_identifier() != channel->identifier()) { - BUG() << "Channel identifier doesn't match in OpenChannel request of type" << channel->type(); + TEGO_BUG() << "Channel identifier doesn't match in OpenChannel request of type" << channel->type(); return false; } if (!connection()->d->insertChannel(channel)) { - BUG() << "Valid channel refused by connection"; + TEGO_BUG() << "Valid channel refused by connection"; return false; } @@ -109,21 +107,21 @@ bool ControlChannel::allowInboundChannelRequest(const Data::Control::OpenChannel { Q_UNUSED(request); Q_UNUSED(result); - BUG() << "ControlChannel should never receive channel requests"; + TEGO_BUG() << "ControlChannel should never receive channel requests"; return false; } bool ControlChannel::allowOutboundChannelRequest(Data::Control::OpenChannel *request) { Q_UNUSED(request); - BUG() << "ControlChannel should never send channel requests"; + TEGO_BUG() << "ControlChannel should never send channel requests"; return false; } bool ControlChannel::processChannelOpenResult(const Data::Control::ChannelResult *result) { Q_UNUSED(result); - BUG() << "ControlChannel should never receive a channel request response"; + TEGO_BUG() << "ControlChannel should never receive a channel request response"; return false; } @@ -176,12 +174,12 @@ void ControlChannel::handleOpenChannel(const Data::Control::OpenChannel &message } else { if (!channel->d_ptr->openChannelInbound(&message, response)) { if (response->opened()) - BUG() << "openChannelInbound handler failed but response said successful. Assuming failure."; + TEGO_BUG() << "openChannelInbound handler failed but response said successful. Assuming failure."; response->set_opened(false); } if (!response->has_opened()) { - BUG() << "inboundOpenChannel handler for" << channel->type() << "did not update response message"; + TEGO_BUG() << "inboundOpenChannel handler for" << channel->type() << "did not update response message"; response->set_opened(false); response->set_common_error(Data::Control::ChannelResult::GenericError); } @@ -191,7 +189,7 @@ void ControlChannel::handleOpenChannel(const Data::Control::OpenChannel &message if (!channel || !channel->isOpened() || channel->direction() != Inbound || channel->identifier() != id) { - BUG() << "Channel" << channel->type() << "in unexpected state after inbound open"; + TEGO_BUG() << "Channel" << channel->type() << "in unexpected state after inbound open"; response->set_opened(false); // The channel may think it's open, so force it to close channel->closeChannel(); @@ -235,10 +233,10 @@ void ControlChannel::handleChannelResult(const Data::Control::ChannelResult &mes bool opened = channel->d_ptr->openChannelResult(&message); if (opened && !channel->isOpened()) { - BUG() << "Outbound channel isn't open after successful ChannelResult"; + TEGO_BUG() << "Outbound channel isn't open after successful ChannelResult"; channel->closeChannel(); } else if (!opened && channel->isOpened()) { - BUG() << "Outbound channel is open after failed ChannelResult"; + TEGO_BUG() << "Outbound channel is open after failed ChannelResult"; channel->closeChannel(); } @@ -246,7 +244,7 @@ void ControlChannel::handleChannelResult(const Data::Control::ChannelResult &mes // instance to be deleted once it's safe to do so if (!opened || !channel->isOpened()) { if (connection()->channel(channel->identifier())) { - BUG() << "Channel not invalidated after failed outbound OpenChannel request"; + TEGO_BUG() << "Channel not invalidated after failed outbound OpenChannel request"; channel->closeChannel(); } } else { diff --git a/src/protocol/ControlChannel.h b/src/libtego/source/protocol/ControlChannel.h similarity index 100% rename from src/protocol/ControlChannel.h rename to src/libtego/source/protocol/ControlChannel.h diff --git a/src/protocol/ControlChannel.proto b/src/libtego/source/protocol/ControlChannel.proto similarity index 98% rename from src/protocol/ControlChannel.proto rename to src/libtego/source/protocol/ControlChannel.proto index 7d665167..40e3615c 100644 --- a/src/protocol/ControlChannel.proto +++ b/src/libtego/source/protocol/ControlChannel.proto @@ -1,3 +1,5 @@ +syntax = "proto2"; + package Protocol.Data.Control; message Packet { diff --git a/src/libtego/source/protocol/FileChannel.cpp b/src/libtego/source/protocol/FileChannel.cpp new file mode 100644 index 00000000..1bb59823 --- /dev/null +++ b/src/libtego/source/protocol/FileChannel.cpp @@ -0,0 +1,854 @@ +/* Ricochet Refresh - https://ricochetrefresh.net/ + * Copyright (C) 2020, Blueprint For Free Speech + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "FileChannel.h" +#include "Channel_p.h" +#include "Connection.h" +#include "utils/SecureRNG.h" +#include "utils/Useful.h" + +#include "context.hpp" +#include "error.hpp" +#include "globals.hpp" +#include "file_hash.hpp" +using tego::g_globals; + +using namespace Protocol; + +static void logTransferStats(qint64 bytes, std::chrono::time_point beginTime) +{ + // This is preferred over `static_cast(bytes) / 1024.0` because + // doing it that way will potentially lose precision if the byte count is + // above 9007199254740992, which while albeit unlikely, is theoretically + // possible. + const auto kilobytes = static_cast(bytes / 1024) + (static_cast(bytes % 1024) / 1024); + const auto seconds = std::chrono::duration_cast>( std::chrono::system_clock::now() - beginTime).count(); + + logger::println("Transfer Complete: {{ size : {} kilobytes, duration : {} seconds, rate : {} kilobytes / second}}", kilobytes, seconds, kilobytes / seconds); +} + +// +// Outgoing Transfer Record +// + +FileChannel::outgoing_transfer_record::outgoing_transfer_record( + tego_file_transfer_id_t transferId, + const std::string& filePath, + tego_file_size_t fileSize) +: id(transferId) +, size(fileSize) +, offset(0) +, stream(filePath, std::ios::in | std::ios::binary) +{ } + +// +// Incoming Transfer Record +// + +FileChannel::incoming_transfer_record::incoming_transfer_record( + tego_file_transfer_id_t transferId, + tego_file_size_t fileSize, + const std::string& fileHash) +: id(transferId) +, size(fileSize) +, hash(fileHash) +, stream() +{ } + +FileChannel::incoming_transfer_record::~incoming_transfer_record() +{ + if (this->stream.is_open()) + { + // try our best to remove the partial file + this->stream.close(); + + // ignore error here, if incoming request succeeded then the + // partial should no longer exist + QFile::remove(QString::fromStdString(this->partial_dest())); + } +} + +std::string FileChannel::incoming_transfer_record::partial_dest() const +{ + return dest + ".part"; +} + +void FileChannel::incoming_transfer_record::open_stream(const std::string& destination) +{ + this->dest = destination; + + // attempt to open the destination for reading and writing + // discard previous contents + // binary mode + // we need to read to validate the hash after the transfer completes + this->stream.open(this->partial_dest(), std::ios::in | std::ios::out | std::ios::trunc | std::ios::binary); + TEGO_THROW_IF_FALSE(this->stream.is_open()); +} + +// +// File Channel +// + +FileChannel::FileChannel(Direction direction, Connection *connection) + : Channel(QStringLiteral("im.ricochet.file-transfer"), direction, connection) +{ + connect(this->d_ptr->connection, &Connection::closed, this, &FileChannel::onConnectionClosed); +} + +bool FileChannel::allowInboundChannelRequest( + const Data::Control::OpenChannel*, + Data::Control::ChannelResult *result) +{ + if (connection()->purpose() != Connection::Purpose::KnownContact) { + qDebug() << "Rejecting request for" << type() << "channel from connection with purpose" << int(connection()->purpose()); + result->set_common_error(Data::Control::ChannelResult::UnauthorizedError); + return false; + } + + if (connection()->findChannel(Channel::Inbound)) { + qDebug() << "Rejecting request for" << type() << "channel because one is already open"; + return false; + } + + return true; +} + +bool FileChannel::allowOutboundChannelRequest( + Data::Control::OpenChannel*) +{ + if (connection()->findChannel(Channel::Outbound)) { + TEGO_BUG() << "Rejecting outbound request for" << type() << "channel because one is already open on this connection"; + return false; + } + + if (connection()->purpose() != Connection::Purpose::KnownContact) { + TEGO_BUG() << "Rejecting outbound request for" << type() << "channel for connection with unexpected purpose" << int(connection()->purpose()); + return false; + } + + return true; +} + +bool FileChannel::verifyPacket(Data::File::Packet const& message) +{ + // ensure the packet has only 1 of the possible file messages + + auto messageCount = 0; + messageCount += message.has_file_header(); + messageCount += message.has_file_header_ack(); + messageCount += message.has_file_chunk(); + messageCount += message.has_file_header_response(); + messageCount += message.has_file_chunk_ack(); + messageCount += message.has_file_transfer_complete_notification(); + + if (messageCount == 1) + { + if (message.has_file_header()) { + return verifyFileHeader(message.file_header()); + } else if (message.has_file_header_ack()) { + return verifyFileHeaderAck(message.file_header_ack()); + } else if (message.has_file_chunk()) { + return verifyFileChunk(message.file_chunk()); + } else if (message.has_file_header_response()) { + return verifyFileHeaderResponse(message.file_header_response()); + } else if (message.has_file_chunk_ack()) { + return verifyFileChunkAck(message.file_chunk_ack()); + } else if (message.has_file_transfer_complete_notification()) { + return verifyFileTransferCompleteNotification(message.file_transfer_complete_notification()); + } + } + + return false; +} + +bool FileChannel::verifyFileHeader(Data::File::FileHeader const& message) +{ + return message.has_file_id() && + message.has_file_size() && + message.has_name() && + message.has_file_hash(); +} + +bool FileChannel::verifyFileHeaderAck(Data::File::FileHeaderAck const& message) +{ + return message.has_file_id() && message.has_accepted(); +} + +bool FileChannel::verifyFileHeaderResponse(Data::File::FileHeaderResponse const& message) +{ + return message.has_file_id() && message.has_response(); +} + +bool FileChannel::verifyFileChunk(Data::File::FileChunk const& message) +{ + return message.has_file_id() && message.has_chunk_data(); +} + +bool FileChannel::verifyFileChunkAck(Data::File::FileChunkAck const& message) +{ + return message.has_file_id() && message.has_bytes_received(); +} + +bool FileChannel::verifyFileTransferCompleteNotification(Data::File::FileTransferCompleteNotification const& message) +{ + return message.has_file_id() && message.has_result(); +} + +void FileChannel::receivePacket(const QByteArray &packet) +{ + Data::File::Packet message; + if (!message.ParseFromArray(packet.constData(), packet.size())) { + emitFatalError("Failed to parse message on file channel", tego_file_transfer_result_failure, true); + return; + } + + if (!verifyPacket(message)) + { + emitFatalError("Failed to verify message on file channel", tego_file_transfer_result_failure, true); + return; + } + + if (message.has_file_header()) { + handleFileHeader(message.file_header()); + } else if (message.has_file_header_ack()) { + handleFileHeaderAck(message.file_header_ack()); + } else if (message.has_file_chunk()) { + handleFileChunk(message.file_chunk()); + } else if (message.has_file_header_response()) { + handleFileHeaderResponse(message.file_header_response()); + } else if (message.has_file_chunk_ack()) { + handleFileChunkAck(message.file_chunk_ack()); + } else if (message.has_file_transfer_complete_notification()) { + handleFileTransferCompleteNotification(message.file_transfer_complete_notification()); + } else { + emitFatalError("Unrecognized file packet on FileChannel", tego_file_transfer_result_failure, true); + } +} + +void FileChannel::onConnectionClosed() +{ + // we do not need to close the channel here because our owning Connection + // will already do so, from ConnectionPrivate::socketDisconnected + this->emitFatalError("Connection Closed", tego_file_transfer_result_network_error, false); +} + +// +// Error Handling +// + +void FileChannel::emitFatalError(std::string&& message, tego_file_transfer_result_t error, bool shouldCloseChannel) +{ + qWarning() << message.data(); + + // tear down all ongoing transfers + switch(direction()) + { + case Inbound: + for(const auto& [id, itr] : incomingTransfers) + { + emit this->fileTransferFinished(id, tego_file_transfer_direction_receiving, error); + } + incomingTransfers.clear(); + break; + case Outbound: + for(const auto& [id, itr] : outgoingTransfers) + { + emit this->fileTransferFinished(id, tego_file_transfer_direction_sending, error); + } + outgoingTransfers.clear(); + break; + default: + break; + } + + if (shouldCloseChannel) + { + qWarning() << "Closing Channel"; + this->closeChannel(); + } +} + +void FileChannel::emitNonFatalError(std::string&& message, tego_file_transfer_id_t id, tego_file_transfer_result_t error) +{ + // log error message to console + qWarning() << message.data(); + + const auto direction = this->direction(); + switch(direction) + { + case Inbound: + if (auto it = incomingTransfers.find(id); it != incomingTransfers.end()) + { + emit this->fileTransferFinished(id, tego_file_transfer_direction_receiving, error); + incomingTransfers.erase(it); + } + break; + case Outbound: + if (auto it = outgoingTransfers.find(id); it != outgoingTransfers.end()) + { + emit this->fileTransferFinished(id, tego_file_transfer_direction_sending, error); + outgoingTransfers.erase(it); + } + break; + default: + emitFatalError(fmt::format("Unknown FileChannel::direction()", direction), tego_file_transfer_result_failure, true); + break; + } +} + +// verify that all the file_id members are the right size +template +constexpr static bool has_compatible_file_id() +{ + typedef decltype(S().file_id()) file_id_t; + return std::numeric_limits::max() == std::numeric_limits::max() && + std::numeric_limits::min() == std::numeric_limits::min() && + std::is_signed_v == std::is_signed_v; +} + +static_assert(has_compatible_file_id()); +static_assert(has_compatible_file_id()); +static_assert(has_compatible_file_id()); +static_assert(has_compatible_file_id()); +static_assert(has_compatible_file_id()); +static_assert(has_compatible_file_id()); + + +void FileChannel::handleFileHeader(const Data::File::FileHeader &message) +{ + Q_ASSERT(direction() == Inbound); + + auto response = std::make_unique(); + response->set_accepted(false); + + if (message.name().find("..") != std::string::npos) + { + qWarning() << "Rejected file header with name containing '..'"; + } + else if (message.name().find("/") != std::string::npos) + { + qWarning() << "Rejected file header with name containing '/'"; + } + // ensure the hash is the correct length + else if (message.file_hash().size() != tego_file_hash::DIGEST_SIZE) + { + qWarning() << "Rejected file header with hash incorrect length"; + } + else + { + // ensure that we can write a file this large + if constexpr(std::numeric_limits::max() > std::numeric_limits::max()) + { + if(message.file_size() > std::numeric_limits::max()) + { + qWarning() << "Rejected file header with too large a file size"; + } + } + + tego_file_hash fileHash; + const auto& digest = message.file_hash(); + // copy our digest in directly + std::copy(digest.begin(), digest.end(), fileHash.data.begin()); + + const auto id = message.file_id(); + incoming_transfer_record ifr(id, message.file_size(), fileHash.to_string()); + + // signal the file transfer request + emit this->fileTransferRequestReceived(id, QString::fromStdString(message.name()), ifr.size, std::move(fileHash)); + + incomingTransfers.insert({id, std::move(ifr)}); + + response->set_file_id(id); + response->set_accepted(true); + } + + // finally send our ack for the header + Data::File::Packet packet; + packet.set_allocated_file_header_ack(response.release()); + Channel::sendMessage(packet); +} + +void FileChannel::handleFileHeaderAck(const Data::File::FileHeaderAck &message) +{ + if (direction() != Outbound) { + qWarning() << "Rejected inbound acknowledgement on an inbound file channel"; + closeChannel(); + return; + } + + auto id = message.file_id(); + if (outgoingTransfers.contains(id)) + { + emit this->fileTransferAcknowledged(id, message.accepted()); + } else { + qDebug() << "Received file acknowledgement for unknown message" << id; + } +} + +void FileChannel::handleFileHeaderResponse(const Data::File::FileHeaderResponse &message) +{ + if (direction() != Outbound) { + emitFatalError("Rejected FileHeaderResponse message on inbound file channel", tego_file_transfer_result_failure, true); + return; + } + + const auto id = message.file_id(); + + auto it = outgoingTransfers.find(id); + if (it == outgoingTransfers.end()) + { + // this can happen if local user cancels a transfer request before their receiver has responded, + // so this is not a fatal error + qWarning() << "recieved response for a file header we never sent"; + return; + } + + const auto response = message.response(); + emit this->fileTransferRequestResponded(message.file_id(), static_cast(response)); + + if (response == tego_file_transfer_response_accept) + { + sendNextChunk(id); + it->second.beginTime = std::chrono::system_clock::now(); + } + else + { + if (response != tego_file_transfer_response_reject) + { + // this can never happen kill connection if we receive invalid value here + emitFatalError("Received invalid FileHeaderResponse", tego_file_transfer_result_failure, true); + return; + } + // receiver rejected our transfer request, so erase it from our records + outgoingTransfers.erase(it); + } +} + +void FileChannel::handleFileChunk(const Data::File::FileChunk &message) +{ + if (direction() != Inbound) + { + emitFatalError("Rejected FileChunk message on outbound file channel", tego_file_transfer_result_failure, true); + return; + } + + auto it = incomingTransfers.find(message.file_id()); + if (it == incomingTransfers.end()) + { + // we can receive an unknown chunk if we cancel in the middle of transmission + // so this is fine + qWarning() << "rejecting chunk for unknown file"; + return; + } + else if (message.chunk_data().size() > FileMaxChunkSize) + { + // something is very wrong in this case + emitFatalError("Rejected FileChunk because of invalid chunk_data() size", tego_file_transfer_result_failure, true); + return; + } + else + { + auto& itr = it->second; + const auto& chunk_data = message.chunk_data(); + itr.stream.write(chunk_data.data(), static_cast(chunk_data.size())); + + // emit progress callback + const auto id = message.file_id(); + const auto streamOffset = static_cast(itr.stream.tellg()); + if (streamOffset == std::streamoff(-1)) + { + // we should send complete message to sender if we have a disk error so they do not spam us with chunks + // we can't do anything with; this transfer is not recoverable, but others can continue + emitNonFatalError("Error writing chunk to stream", id, tego_file_transfer_result_filesystem_error); + + // send message to transfer partner to let them know we've given up + auto notification = std::make_unique(); + notification->set_file_id(id); + notification->set_result(Protocol::Data::File::Cancelled); + + Data::File::Packet packet; + packet.set_allocated_file_transfer_complete_notification(notification.release()); + Channel::sendMessage(packet); + + return; + } + + const auto bytesWritten = static_cast(streamOffset); + const auto& bytesTotal = itr.size; + + emit this->fileTransferProgress(id, tego_file_transfer_direction_receiving, bytesWritten, bytesTotal); + + auto response = std::make_unique(); + response->set_file_id(message.file_id()); + response->set_bytes_received(bytesWritten); + + Data::File::Packet ackPacket; + ackPacket.set_allocated_file_chunk_ack(response.release()); + Channel::sendMessage(ackPacket); + + if (bytesWritten == bytesTotal) + { + // reset the read/write stream and calculate the file hash + itr.stream.seekg(0); + tego_file_hash fileHash(itr.stream); + itr.stream.close(); + + if (fileHash.to_string() != itr.hash) + { + // delete file if calculated hash doesn't match expected + QFile::remove(QString::fromStdString(itr.partial_dest())); + emit this->fileTransferFinished(id, tego_file_transfer_direction_receiving, tego_file_transfer_result_bad_hash); + } + else + { + // if a file already exists at our final destination, then remove it + const auto qDest = QString::fromStdString(itr.dest); + if (QFile::exists(qDest)) + { + QFile::remove(qDest); + } + + // move our partial file to final destination + const auto qPartialDest = QString::fromStdString(itr.partial_dest()); + if(QFile::rename(qPartialDest, qDest)) + { + emit this->fileTransferFinished(id, tego_file_transfer_direction_receiving, tego_file_transfer_result_success); + logTransferStats(static_cast(itr.size), itr.beginTime); + } + else + { + emit this->fileTransferFinished(id, tego_file_transfer_direction_receiving, tego_file_transfer_result_filesystem_error); + } + } + incomingTransfers.erase(it); + + // send complete notification to remote user + auto notification = std::make_unique(); + notification->set_file_id(id); + notification->set_result(Protocol::Data::File::Success); + + Data::File::Packet notifPacket; + notifPacket.set_allocated_file_transfer_complete_notification(notification.release()); + Channel::sendMessage(notifPacket); + } + } +} + +void FileChannel::handleFileChunkAck(const Data::File::FileChunkAck &message) +{ + if (direction() != Outbound) + { + emitFatalError("Rejected FileChunkAck message on incoming file channel", tego_file_transfer_result_failure, true); + return; + } + + const auto id = message.file_id(); + + auto it = outgoingTransfers.find(id); + if (it == outgoingTransfers.end()) + { + // we can get here if the sender cancels a transfer and an ack comes in from previously sent chunk + // not an error + qWarning() << "recieved ack for a chunk we never sent"; + return; + } + + const auto& otr = it->second; + + // verify the ack corresponds to how many bytes we've sent + if (message.bytes_received() != otr.offset) + { + // acks currently always come between sending chunks, so our bytes sent and their bytes received should + // not diverge + emitFatalError("mismatch between bytes we have sent and the bytes the receiver claims to have received", tego_file_transfer_result_failure, true); + return; + } + + emit this->fileTransferProgress(otr.id, tego_file_transfer_direction_receiving, otr.offset, otr.size); + + // send the next chunk until we are done + if(otr.offset < otr.size) + { + sendNextChunk(id); + } +} + +// statically verify that our tego_file_transfer_result_t enum matches the FileTransferResult enum +typedef int file_transfer_result_underlying_t; +constexpr bool operator==(Protocol::Data::File::FileTransferResult left, tego_file_transfer_result_t right) +{ + return static_cast(left) == static_cast(right); +} + +constexpr bool operator==(tego_file_transfer_result_t left, Protocol::Data::File::FileTransferResult right) +{ + return right == left; +} + +static_assert(Protocol::Data::File::Success == tego_file_transfer_result_success); +static_assert(Protocol::Data::File::Cancelled == tego_file_transfer_result_cancelled); +static_assert(Protocol::Data::File::Failure == tego_file_transfer_result_failure); + +void FileChannel::handleFileTransferCompleteNotification(const Data::File::FileTransferCompleteNotification &message) +{ + const auto id = message.file_id(); + + Q_ASSERT(direction() == Outbound || direction() == Inbound); + + switch(direction()) + { + case Inbound: + if( auto it = incomingTransfers.find(id); it != incomingTransfers.end()) + { + incomingTransfers.erase(it); + emit fileTransferFinished(id, tego_file_transfer_direction_receiving, static_cast(message.result())); + return; + } + break; + case Outbound: + if (auto it = outgoingTransfers.find(id); it != outgoingTransfers.end()) + { + const auto& otr = it->second; + if (message.result() == tego_file_transfer_result_success) + { + logTransferStats(static_cast(otr.size), otr.beginTime); + } + + outgoingTransfers.erase(it); + emit fileTransferFinished(id, tego_file_transfer_direction_sending, static_cast(message.result())); + return; + } + break; + default: + break; + } + // we could get here if we cancel a transfer locally before the last chunk has been ack'd, so not an error + qWarning() << "received cancel request for unknown transfer:" << id; +} + +bool FileChannel::sendFileWithId(QString file_uri, + tego_file_hash_t const& file_hash, + QDateTime, + tego_file_transfer_id_t file_id) +{ + Q_ASSERT(direction() == Outbound); + Q_ASSERT(!outgoingTransfers.contains(file_id)); + + // verify the args + Q_ASSERT(!file_uri.isEmpty()); + + /* only allow regular files or symlinks chains to regular files */ + QFileInfo fi(file_uri); + if (!fi.exists()) + { + // this error state is bubbled up to ConversationModel + qWarning() << "File does not exist"; + return false; + } + + // canonical file path must not be empty if the file exists + const auto canonicalFilePath = fi.canonicalFilePath(); + Q_ASSERT(!canonicalFilePath.isEmpty()); + + // file size must be positive, QFileInfo::size() returns signed 64 bit int, so so long as + // we are positive we'll fit into a tego_file_size_t which is a 64 bit unsigned int + Q_ASSERT(fi.size() > 0); + + // ensure this file's size can be represented as a std::streamoff (the integer type of our offset into a std::ofstream) + // if std::streamoff is a smaller type than qint64 (we only need to do this if streamoff is smaller than qint64) + if constexpr(std::numeric_limits::max() < std::numeric_limits::max()) + { + TEGO_THROW_IF_FALSE(fi.size() <= std::numeric_limits::max()); + } + + const auto fileSize = static_cast(fi.size()); + + // create our record + const auto filePath = canonicalFilePath.toStdString(); + outgoing_transfer_record otr(file_id, filePath, fileSize); + if (!otr.stream.is_open()) + { + qWarning() << "Failed to open file for sending header"; + // this error state is bubbled up to ConversationModel + return false; + } + outgoingTransfers.insert({file_id, std::move(otr)}); + + // send file header to recipient + auto header = std::make_unique(); + header->set_file_id(file_id); + header->set_file_size(fileSize); + header->set_file_hash(file_hash.data.data(), file_hash.data.size()); + header->set_name(fi.fileName().toStdString()); + + Data::File::Packet packet; + packet.set_allocated_file_header(header.release()); + + Channel::sendMessage(packet); + + // the first chunk will get sent after the header reponse + return true; +} + +void FileChannel::acceptFile(tego_file_transfer_id_t id, const std::string& dest) +{ + auto it = incomingTransfers.find(id); + TEGO_THROW_IF_FALSE(it != incomingTransfers.end()); + auto& itr = it->second; + + itr.beginTime = std::chrono::system_clock::now(); + itr.open_stream(dest); + + auto response = std::make_unique(); + response->set_response(tego_file_transfer_response_accept); + response->set_file_id(id); + + Data::File::Packet packet; + packet.set_allocated_file_header_response(response.release()); + Channel::sendMessage(packet); + + // emit starting transfer progress callback + emit this->fileTransferProgress(id, tego_file_transfer_direction_receiving, 0, it->second.size); +} + +void FileChannel::rejectFile(tego_file_transfer_id_t id) +{ + auto it = incomingTransfers.find(id); + TEGO_THROW_IF_FALSE(it != incomingTransfers.end()); + + // remove the incoming_transfer_record from our list on reject + incomingTransfers.erase(it); + + auto response = std::make_unique(); + response->set_response(tego_file_transfer_response_reject); + response->set_file_id(id); + + Data::File::Packet packet; + packet.set_allocated_file_header_response(response.release()); + Channel::sendMessage(packet); + + // emit completion callback + emit fileTransferFinished(id, tego_file_transfer_direction_receiving, tego_file_transfer_result_rejected); +} + +bool FileChannel::cancelTransfer(tego_file_transfer_id_t id) +{ + // verify the transfer exists in our system + switch(direction()) + { + case Inbound: + if (auto it = incomingTransfers.find(id); it != incomingTransfers.end()) + { + incomingTransfers.erase(it); + } + else + { + return false; + } + break; + case Outbound: + if (auto it = outgoingTransfers.find(id); it != outgoingTransfers.end()) + { + outgoingTransfers.erase(it); + } + else + { + return false; + } + break; + default: + return false; + } + + // finally send cancel notification to remote user + auto notification = std::make_unique(); + notification->set_file_id(id); + notification->set_result(Protocol::Data::File::Cancelled); + + Data::File::Packet packet; + packet.set_allocated_file_transfer_complete_notification(notification.release()); + Channel::sendMessage(packet); + + emit fileTransferFinished(id, tego_file_transfer_direction_receiving, tego_file_transfer_result_cancelled); + + return true; +} + +void FileChannel::sendNextChunk(tego_file_transfer_id_t id) +{ + Q_ASSERT(direction() == Outbound); + + if (auto it = outgoingTransfers.find(id); it != outgoingTransfers.end()) + { + auto& otr = it->second; + + // make sure our offset and the stream offset agree + Q_ASSERT(otr.finished() == false); + Q_ASSERT(otr.offset == static_cast(otr.stream.tellg())); + + // read the next chunk to our buffer, and update our offset + otr.stream.read(this->chunkBuffer, FileMaxChunkSize); + const auto chunkSize = otr.stream.gcount(); + // ensure we read a valid value + static_assert(FileMaxChunkSize != std::numeric_limits::max()); + if (chunkSize == std::numeric_limits::max()) + { + // not quite a fatal error, but we need to cleanup this transfer + emitNonFatalError("Problem reading the next chunk from disk", id, tego_file_transfer_result_filesystem_error); + + // send message to transfer partner to let them know we've given up + auto notification = std::make_unique(); + notification->set_file_id(id); + notification->set_result(Protocol::Data::File::Cancelled); + + Data::File::Packet packet; + packet.set_allocated_file_transfer_complete_notification(notification.release()); + Channel::sendMessage(packet); + + return; + } + Q_ASSERT(static_cast(chunkSize) <= FileMaxChunkSize); + + otr.offset += static_cast(chunkSize); + + // build our chunk + auto chunk = std::make_unique(); + chunk->set_file_id(id); + chunk->set_chunk_data(std::begin(chunkBuffer), static_cast(chunkSize)); + + Data::File::Packet packet; + packet.set_allocated_file_chunk(chunk.release()); + + // send the chunk + Channel::sendMessage(packet); + } +} diff --git a/src/libtego/source/protocol/FileChannel.h b/src/libtego/source/protocol/FileChannel.h new file mode 100644 index 00000000..688939d1 --- /dev/null +++ b/src/libtego/source/protocol/FileChannel.h @@ -0,0 +1,160 @@ +/* Ricochet Refresh - https://ricochetrefresh.net/ + * Copyright (C) 2020, Blueprint For Free Speech + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef PROTOCOL_FILECHANNEL_H +#define PROTOCOL_FILECHANNEL_H + +#include "protocol/Channel.h" +#include "FileChannel.pb.h" +#include "tego/tego.h" +#include "file_hash.hpp" + +namespace Protocol +{ + +class FileChannel : public Channel +{ + Q_OBJECT + Q_DISABLE_COPY(FileChannel) + +public: + explicit FileChannel(Direction direction, Connection *connection); + + bool sendFileWithId(QString file_url, const tego_file_hash_t& fileHash, QDateTime time, tego_file_transfer_id_t id); + void acceptFile(tego_file_transfer_id_t id, const std::string& dest); + void rejectFile(tego_file_transfer_id_t id); + bool cancelTransfer(tego_file_transfer_id_t id); + // signals bubble up to the ConversationModel object that owns this FileChannel +signals: + void fileTransferRequestReceived(tego_file_transfer_id_t id, QString fileName, tego_file_size_t fileSize, tego_file_hash_t); + void fileTransferAcknowledged(tego_file_transfer_id_t id, bool ack); + void fileTransferRequestResponded(tego_file_transfer_id_t id, tego_file_transfer_response_t response); + void fileTransferProgress(tego_file_transfer_id_t id, tego_file_transfer_direction_t direction, tego_file_size_t bytesTransmitted, tego_file_size_t bytesTotal); + void fileTransferFinished(tego_file_transfer_id_t id, tego_file_transfer_direction_t direction, tego_file_transfer_result_t); + +protected: + virtual bool allowInboundChannelRequest(const Data::Control::OpenChannel *request, Data::Control::ChannelResult *result); + virtual bool allowOutboundChannelRequest(Data::Control::OpenChannel *request); + virtual void receivePacket(const QByteArray &packet); +private: + // when our socket goes away + void onConnectionClosed(); + + // we need runtime checks to ensure that sizes stored as tego_file_size_t are representable as + // std::streamoff too where appropriate + // verify that std::streamoff is representable as a tego_file_size_t + static_assert(std::numeric_limits::max() <= std::numeric_limits::max()); + // verify the QFileInfo::size() method returns a qint64 + static_assert(std::is_same_v); + // verify that std::streamoff is representable as qint64 (type used by Qt File APIs for sizes) + static_assert(std::numeric_limits::max() <= std::numeric_limits::max()); + + struct outgoing_transfer_record + { + outgoing_transfer_record( + tego_file_transfer_id_t id, + const std::string& filePath, + tego_file_size_t fileSize); + + std::chrono::time_point beginTime; + + const tego_file_transfer_id_t id; + const tego_file_size_t size; + tego_file_size_t offset; + std::ifstream stream; + + inline bool finished() const { return offset == size; } + }; + + struct incoming_transfer_record + { + incoming_transfer_record( + tego_file_transfer_id_t id, + tego_file_size_t fileSize, + const std::string& fileHash); + // explicit destructor defined, so we need to explicitly define a move constructor + // for usage with std::map + incoming_transfer_record(incoming_transfer_record&&) = default; + + ~incoming_transfer_record(); + + std::chrono::time_point beginTime; + + const tego_file_transfer_id_t id; + const tego_file_size_t size; + std::string dest; // destination to save to + const std::string hash; + + // need to write and read + std::fstream stream; + + std::string partial_dest() const; + void open_stream(const std::string& dest); + }; + // 63 kb, max packet size is UINT16_MAX (ak 65535, 64k - 1) so leave space for other data + constexpr static tego_file_size_t FileMaxChunkSize = 63*1024; // bytes + // intermediate buffer we load chunks from disk into + // each access to this buffer happens on the same thread, and only within the scope of a function + // so no need to worry about synchronization or sharing between file transfers + char chunkBuffer[FileMaxChunkSize]; + + // file transfers we are sending + std::map outgoingTransfers; + // file transfers we are receiving + std::map incomingTransfers; + + // called when something unrecoverable occurs, or contact is sending us bad packets, or we get in + // some other allegedly impossible state; kills all our transfers and disconnect the channel + void emitFatalError(std::string&& msg, tego_file_transfer_result_t error, bool shouldCloseChannel); + // called when some error occurs that does not affect other transfers + void emitNonFatalError(std::string&& msg, tego_file_transfer_id_t id, tego_file_transfer_result_t error); + + bool verifyPacket(Data::File::Packet const& message); + bool verifyFileHeader(Data::File::FileHeader const& message); + bool verifyFileHeaderAck(Data::File::FileHeaderAck const& message); + bool verifyFileHeaderResponse(Data::File::FileHeaderResponse const& message); + bool verifyFileChunk(Data::File::FileChunk const& message); + bool verifyFileChunkAck(Data::File::FileChunkAck const& message); + bool verifyFileTransferCompleteNotification(Data::File::FileTransferCompleteNotification const& message); + + void handleFileHeader(const Data::File::FileHeader &message); + void handleFileHeaderAck(const Data::File::FileHeaderAck &message); + void handleFileHeaderResponse(const Data::File::FileHeaderResponse &message); + void handleFileChunk(const Data::File::FileChunk &message); + void handleFileChunkAck(const Data::File::FileChunkAck &message); + void handleFileTransferCompleteNotification(const Data::File::FileTransferCompleteNotification &message); + + void sendNextChunk(tego_file_transfer_id_t id); +}; + +} +#endif diff --git a/src/libtego/source/protocol/FileChannel.proto b/src/libtego/source/protocol/FileChannel.proto new file mode 100644 index 00000000..df06eac9 --- /dev/null +++ b/src/libtego/source/protocol/FileChannel.proto @@ -0,0 +1,81 @@ +/* Ricochet Refresh - https://ricochetrefresh.net/ + * Copyright (C) 2020, Blueprint For Free Speech + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +syntax = "proto2"; + +package Protocol.Data.File; + +message Packet { + optional FileHeader file_header = 1; + optional FileHeaderAck file_header_ack = 2; + optional FileHeaderResponse file_header_response = 3; + optional FileChunk file_chunk = 4; + optional FileChunkAck file_chunk_ack = 5; + optional FileTransferCompleteNotification file_transfer_complete_notification = 6; +} + +message FileHeader { + optional uint32 file_id = 1; + optional uint64 file_size = 2; + optional string name = 3; + optional bytes file_hash = 4; +} + +message FileHeaderAck { + optional uint32 file_id = 1; + optional bool accepted = 2 [default = false]; +} + +message FileHeaderResponse { + optional uint32 file_id = 1; + optional int32 response = 2; +} + +message FileChunk { + optional uint32 file_id = 1; + optional bytes chunk_data = 2; +} +message FileChunkAck { + optional uint32 file_id = 1; + optional uint64 bytes_received = 2; +} + +enum FileTransferResult { + Success = 0; + Failure = 1; + Cancelled = 2; +} + +message FileTransferCompleteNotification { + optional uint32 file_id = 1; + optional FileTransferResult result = 2; +} \ No newline at end of file diff --git a/src/protocol/OutboundConnector.cpp b/src/libtego/source/protocol/OutboundConnector.cpp similarity index 93% rename from src/protocol/OutboundConnector.cpp rename to src/libtego/source/protocol/OutboundConnector.cpp index 410bbff5..8c0cd32c 100644 --- a/src/protocol/OutboundConnector.cpp +++ b/src/libtego/source/protocol/OutboundConnector.cpp @@ -35,7 +35,6 @@ #include "tor/TorSocket.h" #include "ControlChannel.h" #include "AuthHiddenServiceChannel.h" -#include using namespace Protocol; @@ -58,9 +57,9 @@ class OutboundConnectorPrivate : public QObject QTimer errorRetryTimer; int errorRetryCount; - OutboundConnectorPrivate(OutboundConnector *q) - : QObject(q) - , q(q) + OutboundConnectorPrivate(OutboundConnector *oc) + : QObject(oc) + , q(oc) , socket(0) , port(0) , status(OutboundConnector::Inactive) @@ -93,7 +92,7 @@ OutboundConnector::~OutboundConnector() void OutboundConnector::setAuthPrivateKey(const CryptoKey &key) { if (!key.isLoaded() || !key.isPrivate()) { - BUG() << "Cannot make outbound connection without a valid private key"; + TEGO_BUG() << "Cannot make outbound connection without a valid private key"; return; } @@ -109,7 +108,7 @@ bool OutboundConnector::connectToHost(const QString &hostname, quint16 port) } if (d->status == Ready) { - BUG() << "Reusing an OutboundConnector object"; + TEGO_BUG() << "Reusing an OutboundConnector object"; d->errorMessage = QStringLiteral("Outbound connection handler was already used"); d->setStatus(Error); return false; @@ -181,7 +180,7 @@ QSharedPointer OutboundConnector::takeConnection() { QSharedPointer c(d->connection); if (status() != Ready || !c) { - BUG() << "Cannot take connection when not in the Ready state"; + TEGO_BUG() << "Cannot take connection when not in the Ready state"; return c; } @@ -241,7 +240,7 @@ void OutboundConnectorPrivate::retryAfterError() void OutboundConnectorPrivate::onConnected() { if (!socket || status != OutboundConnector::Connecting) { - BUG() << "OutboundConnector connected in an unexpected state"; + TEGO_BUG() << "OutboundConnector connected in an unexpected state"; setError(QStringLiteral("Connected in an unexpected state")); return; } @@ -260,6 +259,8 @@ void OutboundConnectorPrivate::onConnected() setError(QStringLiteral("Protocol version negotiation failed with peer")); } ); + /* XXX: this is a pointless connect - it connects a signal to a signal, which has no connections + * (all instances of oldVersionNegotiated are unneeded) */ connect(connection.data(), &Connection::oldVersionNegotiated, q, &OutboundConnector::oldVersionNegotiated); setStatus(OutboundConnector::Initializing); } @@ -267,7 +268,7 @@ void OutboundConnectorPrivate::onConnected() void OutboundConnectorPrivate::startAuthentication() { if (!connection || status != OutboundConnector::Initializing) { - BUG() << "OutboundConnector startAuthentication in an unexpected state"; + TEGO_BUG() << "OutboundConnector startAuthentication in an unexpected state"; setError(QStringLiteral("Connected in an unexpected state")); return; } diff --git a/src/protocol/OutboundConnector.h b/src/libtego/source/protocol/OutboundConnector.h similarity index 94% rename from src/protocol/OutboundConnector.h rename to src/libtego/source/protocol/OutboundConnector.h index ba540596..c024f4bc 100644 --- a/src/protocol/OutboundConnector.h +++ b/src/libtego/source/protocol/OutboundConnector.h @@ -33,8 +33,6 @@ #ifndef PROTOCOL_OUTBOUNDCONNECTOR_H #define PROTOCOL_OUTBOUNDCONNECTOR_H -#include -#include #include "Connection.h" #include "utils/CryptoKey.h" @@ -54,11 +52,6 @@ class OutboundConnector : public QObject { Q_OBJECT Q_DISABLE_COPY(OutboundConnector) - Q_ENUMS(Status) - - Q_PROPERTY(Status status READ status NOTIFY statusChanged) - Q_PROPERTY(bool isActive READ isActive NOTIFY isActiveChanged) - public: enum Status { Inactive, diff --git a/src/libtego/source/signals.cpp b/src/libtego/source/signals.cpp new file mode 100644 index 00000000..68a96652 --- /dev/null +++ b/src/libtego/source/signals.cpp @@ -0,0 +1,154 @@ +#include "signals.hpp" +#include "context.hpp" +#include "error.hpp" +#include "ed25519.hpp" +#include "user.hpp" +#include "file_hash.hpp" + +namespace tego +{ + type_erased_callback::type_erased_callback(type_erased_callback&& that) + { + *this = std::move(that); + } + + type_erased_callback& type_erased_callback::operator=(type_erased_callback&& that) + { + this->data_ = std::move(that.data_); + this->callback_ = that.callback_; + that.callback_ = nullptr; + + return *this; + } + + + void type_erased_callback::invoke() + { + this->callback_(this->data_.get()); + } + + // + // Callback Registery + // + + callback_registry::callback_registry(tego_context* context) + : context_(context) + { + TEGO_THROW_IF_NULL(context); + } + + void callback_registry::push_back(type_erased_callback&& callback) + { + this->context_->callback_queue_.push_back(std::move(callback)); + } + + // + // Callback Queue + // + + callback_queue::callback_queue(tego_context* context) + : context_(context) + , terminating_(false) + , mutex_() + , pending_callbacks_() + , worker_([](tego_context* ctx) -> void + { + auto& self = ctx->callback_queue_; + + // we use double buffering to ensure we can enqueue tasks + // without blocking while we are executing previously + // enqueued tasks + decltype(self.pending_callbacks_) local_queue; + + // we keep spinning until termination is signaled + while(!self.terminating_) + { + // acquire the queue's lock and swap our queues + // the backend can now keep emitting callbacks + // while we work through our queue of old ones + { + std::lock_guard lock(self.mutex_); + std::swap(local_queue, self.pending_callbacks_); + } + + for(auto& callback : local_queue) { + // acquire our context's lock so that we don't have two + // threads potentially modifying internals + std::lock_guard lock(ctx->mutex_); + try + { + callback.invoke(); + } + // swallow any throw exceptions + catch(...) {}; + } + // empty our our local queue + local_queue.clear(); + + // sleep before looking for more work + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + + }, context) + { + // empty constructor + } + + callback_queue::~callback_queue() + { + // signal our worker thread to finish up and terminate + terminating_ = true; + worker_.join(); + } + + void callback_queue::push_back(type_erased_callback&& callback) + { + // acquire our lock and push into our vec + if (!terminating_) + { + std::lock_guard lock(mutex_); + pending_callbacks_.push_back(std::move(callback)); + } + } +} + +// +// Implementation of our exposed callback setters +// + +extern "C" +{ + #define TEGO_DEFINE_CALLBACK_SETTER(EVENT)\ + void tego_context_set_##EVENT##_callback(\ + tego_context_t* context,\ + tego_##EVENT##_callback_t callback,\ + tego_error_t** error)\ + {\ + return tego::translateExceptions([=]() -> void\ + {\ + TEGO_THROW_IF_NULL(context);\ + TEGO_THROW_IF_FALSE(context->threadId == std::this_thread::get_id());\ + context->callback_registry_.register_##EVENT(callback);\ + }, error);\ + } + + TEGO_DEFINE_CALLBACK_SETTER(tor_error_occurred) + TEGO_DEFINE_CALLBACK_SETTER(update_tor_daemon_config_succeeded) + TEGO_DEFINE_CALLBACK_SETTER(tor_control_status_changed) + TEGO_DEFINE_CALLBACK_SETTER(tor_process_status_changed) + TEGO_DEFINE_CALLBACK_SETTER(tor_network_status_changed) + TEGO_DEFINE_CALLBACK_SETTER(tor_bootstrap_status_changed) + TEGO_DEFINE_CALLBACK_SETTER(tor_log_received) + TEGO_DEFINE_CALLBACK_SETTER(host_user_state_changed) + TEGO_DEFINE_CALLBACK_SETTER(chat_request_received) + TEGO_DEFINE_CALLBACK_SETTER(chat_request_response_received) + TEGO_DEFINE_CALLBACK_SETTER(message_received) + TEGO_DEFINE_CALLBACK_SETTER(message_acknowledged) + TEGO_DEFINE_CALLBACK_SETTER(file_transfer_request_received) + TEGO_DEFINE_CALLBACK_SETTER(file_transfer_request_acknowledged) + TEGO_DEFINE_CALLBACK_SETTER(file_transfer_request_response_received) + TEGO_DEFINE_CALLBACK_SETTER(file_transfer_progress) + TEGO_DEFINE_CALLBACK_SETTER(file_transfer_complete) + TEGO_DEFINE_CALLBACK_SETTER(user_status_changed) + TEGO_DEFINE_CALLBACK_SETTER(new_identity_created) +} diff --git a/src/libtego/source/signals.hpp b/src/libtego/source/signals.hpp new file mode 100644 index 00000000..9c14f650 --- /dev/null +++ b/src/libtego/source/signals.hpp @@ -0,0 +1,159 @@ +#pragma once + +namespace tego +{ + // allows us to marshall lambda functions and their data to + // call them later + class type_erased_callback + { + public: + type_erased_callback() = default; + type_erased_callback(type_erased_callback&&); + type_erased_callback& operator=(type_erased_callback&&); + + template + type_erased_callback(FUNC&& func) + { + // heap allocated a moved copy of the passed in function + data_ = { + new FUNC(std::move(func)), + // deleter function casts raw ptr back to FUNC and deletes + [](void* data) { + auto* lambda = static_cast(data); + delete lambda; + } + }; + + // save of a function for invoking func + callback_ = [](void* data) { + auto* lambda = static_cast(data); + (*lambda)(); + }; + } + void invoke(); + private: + std::unique_ptr data_ = {nullptr, nullptr}; + void (*callback_)(void* data) = nullptr; + }; + + /* + * The callback_register class keeps track of provided user callbacks + * and lets us register them via register_X functions. Libtego internals + * trigger callbacks by way of the emit_EVENT functions. The callback + * registry is per-tego_context + */ + + class callback_registry + { + public: + callback_registry(tego_context* context); + + /* + * Each callback X has a register_X function, an emit_X function, and + * a cleanup_X_args function + * + * It is assumed that a callback always sends over the tego_context_t* as + * the first argument + */ + #define TEGO_IMPLEMENT_CALLBACK_FUNCTIONS(EVENT, ...)\ + private:\ + tego_##EVENT##_callback_t EVENT##_ = nullptr;\ + public:\ + void register_##EVENT(tego_##EVENT##_callback_t cb)\ + {\ + EVENT##_ = cb;\ + }\ + template\ + void emit_##EVENT(ARGS&&... args)\ + {\ + if (EVENT##_ != nullptr) {\ + push_back(\ + [=, context=context_, callback=EVENT##_]() mutable -> void\ + {\ + callback(context, std::forward(args)...);\ + cleanup_args(std::forward(args)...);\ + }\ + );\ + }\ + }\ + private: + + TEGO_IMPLEMENT_CALLBACK_FUNCTIONS(tor_error_occurred, tego_tor_error_origin_t, tego_error_t*) + TEGO_IMPLEMENT_CALLBACK_FUNCTIONS(update_tor_daemon_config_succeeded, tego_bool_t) + TEGO_IMPLEMENT_CALLBACK_FUNCTIONS(tor_control_status_changed, tego_tor_control_status_t) + TEGO_IMPLEMENT_CALLBACK_FUNCTIONS(tor_process_status_changed, tego_tor_process_status_t) + TEGO_IMPLEMENT_CALLBACK_FUNCTIONS(tor_network_status_changed, tego_tor_network_status_t) + TEGO_IMPLEMENT_CALLBACK_FUNCTIONS(tor_bootstrap_status_changed, int32_t, tego_tor_bootstrap_tag_t) + TEGO_IMPLEMENT_CALLBACK_FUNCTIONS(tor_log_received, char*, size_t) + TEGO_IMPLEMENT_CALLBACK_FUNCTIONS(host_user_state_changed, tego_host_user_state_t) + TEGO_IMPLEMENT_CALLBACK_FUNCTIONS(chat_request_received, tego_user_id_t*, char*, size_t) + TEGO_IMPLEMENT_CALLBACK_FUNCTIONS(chat_request_response_received, tego_user_id_t*, tego_bool_t) + TEGO_IMPLEMENT_CALLBACK_FUNCTIONS(message_received, tego_user_id_t*, tego_time_t, tego_message_id_t, char*, size_t) + TEGO_IMPLEMENT_CALLBACK_FUNCTIONS(message_acknowledged, tego_user_id_t*, tego_message_id_t, tego_bool_t) + TEGO_IMPLEMENT_CALLBACK_FUNCTIONS(file_transfer_request_received, tego_user_id_t*, tego_file_transfer_id_t, char*, size_t, uint64_t, tego_file_hash_t*) + TEGO_IMPLEMENT_CALLBACK_FUNCTIONS(file_transfer_request_acknowledged, tego_user_id_t*, tego_file_transfer_id_t, tego_bool_t) + TEGO_IMPLEMENT_CALLBACK_FUNCTIONS(file_transfer_request_response_received, tego_user_id_t*, tego_file_transfer_id_t, tego_file_transfer_response_t) + TEGO_IMPLEMENT_CALLBACK_FUNCTIONS(file_transfer_progress, tego_user_id_t*, tego_file_transfer_id_t, tego_file_transfer_direction_t, uint64_t, uint64_t) + TEGO_IMPLEMENT_CALLBACK_FUNCTIONS(file_transfer_complete, tego_user_id_t*, tego_file_transfer_id_t, tego_file_transfer_direction_t, tego_file_transfer_result_t) + TEGO_IMPLEMENT_CALLBACK_FUNCTIONS(user_status_changed, tego_user_id_t*, tego_user_status_t) + TEGO_IMPLEMENT_CALLBACK_FUNCTIONS(new_identity_created, tego_ed25519_private_key_t*) + + + private: + void push_back(type_erased_callback&&); + tego_context* context_ = nullptr; + + // cleanup message buffer + static void cleanup_arg(char* msg) + { + delete[] msg; + } + + // cleanup for other pointer types + template + static void cleanup_arg(T* pVal) + { + delete pVal; + } + + // no-op for unspecialized types + template + static void cleanup_arg(T&&) + { } + + // no-op for termation case + static void cleanup_args() + { } + + template + static void cleanup_args(FIRST&& first, ARGS&&... args) + { + cleanup_arg(std::forward(first)); + return cleanup_args(std::forward(args)...); + } + }; + + /* + * callback_queue holds onto a queue of callbacks. Libtego internals + * enqueue callbacks and the callback queue executes them on a + * background worker thread + */ + class callback_queue + { + public: + callback_queue(tego_context* context); + ~callback_queue(); + + void push_back(type_erased_callback&&); + private: + tego_context* context_; + + std::atomic_bool terminating_; + std::mutex mutex_; + // this queue is protected by mutex_ within worker_ thread and callback_queue methods + std::vector pending_callbacks_; + + // worker thread must be last so that other members are init'd before thread runs + std::thread worker_; + }; +} diff --git a/src/libtego/source/tor.cpp b/src/libtego/source/tor.cpp new file mode 100644 index 00000000..964fca77 --- /dev/null +++ b/src/libtego/source/tor.cpp @@ -0,0 +1,230 @@ +#include "error.hpp" +#include "tor.hpp" + +extern "C" +{ + // + // Tor Launch Configuration + // + + void tego_tor_launch_config_initialize( + tego_tor_launch_config_t** out_launchConfig, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(out_launchConfig); + TEGO_THROW_IF_FALSE(*out_launchConfig == nullptr); + + auto launchConfig = std::make_unique(); + *out_launchConfig = launchConfig.release(); + }, error); + } + + void tego_tor_launch_config_set_data_directory( + tego_tor_launch_config_t* launchConfig, + const char* dataDirectory, + size_t dataDirectoryLength, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(launchConfig); + TEGO_THROW_IF_NULL(dataDirectory); + + logger::println("tor data dir : {}", dataDirectory); + + launchConfig->dataDirectory.assign(dataDirectory, dataDirectoryLength); + }, error); + } + + // + // Tor Daemon Configuration + // + + void tego_tor_daemon_config_initialize( + tego_tor_daemon_config_t** out_daemonConfig, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(out_daemonConfig); + TEGO_THROW_IF_FALSE(*out_daemonConfig == nullptr); + + auto daemonConfig = std::make_unique(); + *out_daemonConfig = daemonConfig.release(); + }, error); + } + + void tego_tor_daemon_config_set_disable_network( + tego_tor_daemon_config_t* config, + tego_bool_t disableNetwork, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(config); + TEGO_THROW_IF_FALSE(disableNetwork == TEGO_TRUE || disableNetwork == TEGO_FALSE); + + config->disableNetwork = (disableNetwork == TEGO_TRUE); + }, error); + } + + void tego_tor_daemon_config_set_proxy_socks4( + tego_tor_daemon_config_t* config, + const char* address, + size_t addressLength, + uint16_t port, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(config); + TEGO_THROW_IF_NULL(address); + TEGO_THROW_IF_FALSE(addressLength > 0); + TEGO_THROW_IF_FALSE(port > 0); + + config->proxy.type = tego_proxy_type_socks4; + config->proxy.address.assign(address, addressLength); + config->proxy.port = port; + }, error); + } + + void tego_tor_daemon_config_set_proxy_socks5( + tego_tor_daemon_config_t* config, + const char* address, + size_t addressLength, + uint16_t port, + const char* username, + size_t usernameLength, + const char* password, + size_t passwordLength, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(config); + TEGO_THROW_IF_NULL(address); + TEGO_THROW_IF_FALSE(addressLength > 0); + TEGO_THROW_IF_FALSE(port > 0); + // if either string is null, their length must also be 0 + TEGO_THROW_IF_FALSE((username == nullptr && usernameLength == 0) || username != nullptr); + TEGO_THROW_IF_FALSE((password == nullptr && passwordLength == 0) || password != nullptr); + + config->proxy.type = tego_proxy_type_socks5; + config->proxy.address.assign(address, addressLength); + config->proxy.port = port; + if (username != nullptr) + { + config->proxy.username.assign(username, usernameLength); + } + if (password != nullptr) + { + config->proxy.password.assign(password, passwordLength); + } + }, error); + } + + void tego_tor_daemon_config_set_proxy_https( + tego_tor_daemon_config_t* config, + const char* address, + size_t addressLength, + uint16_t port, + const char* username, + size_t usernameLength, + const char* password, + size_t passwordLength, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(config); + TEGO_THROW_IF_NULL(address); + TEGO_THROW_IF_FALSE(addressLength > 0); + TEGO_THROW_IF_FALSE(port > 0); + // if either string is null, their length must also be 0 + TEGO_THROW_IF_FALSE((username == nullptr && usernameLength == 0) || username != nullptr); + TEGO_THROW_IF_FALSE((password == nullptr && passwordLength == 0) || password != nullptr); + + config->proxy.type = tego_proxy_type_https; + config->proxy.address.assign(address, addressLength); + config->proxy.port = port; + if (username != nullptr) + { + config->proxy.username.assign(username, usernameLength); + } + if (password != nullptr) + { + config->proxy.password.assign(password, passwordLength); + } + }, error); + } + + void tego_tor_daemon_config_set_allowed_ports( + tego_tor_daemon_config_t* config, + const uint16_t* ports, + size_t portsCount, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(config); + TEGO_THROW_IF_NULL(ports); + TEGO_THROW_IF_FALSE(portsCount > 0); + + // ensure we only put each port in once + std::set portsSet; + portsSet.insert(ports[0]); + for(size_t i = 1; i < portsCount; ++i) + { + const auto currentPort = ports[i]; + auto it = portsSet.find(currentPort); + TEGO_THROW_IF_FALSE(it == portsSet.end()); + + portsSet.insert(currentPort); + } + + auto& allowedPorts = config->allowedPorts; + allowedPorts.clear(); + allowedPorts.reserve(portsCount); + + for(auto currentPort : portsSet) + { + allowedPorts.push_back(currentPort); + } + }, error); + } + + void tego_tor_daemon_config_set_bridges( + tego_tor_daemon_config_t* config, + const char** bridges, + size_t* bridgeLengths, + size_t bridgeCount, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(config); + // ensure we have valid pointers or we are setting 0 bridges + TEGO_THROW_IF_FALSE((bridges != nullptr && bridgeLengths != nullptr) || bridgeCount == 0); + + // ensure each bridge string is valid + for(size_t i = 0; i < bridgeCount; ++i) + { + TEGO_THROW_IF_NULL(bridges[i]); + TEGO_THROW_IF_FALSE(bridgeLengths[i] > 0); + } + + // clear out existing bridge strings and append our new ones + auto& confBridges = config->bridges; + confBridges.clear(); + + // copy bridge strings over + confBridges.reserve(bridgeCount); + for(size_t i = 0; i < bridgeCount; ++i) + { + confBridges.emplace_back(bridges[i], bridgeLengths[i]); + } + }, error); + } +} diff --git a/src/libtego/source/tor.hpp b/src/libtego/source/tor.hpp new file mode 100644 index 00000000..5b6c8583 --- /dev/null +++ b/src/libtego/source/tor.hpp @@ -0,0 +1,33 @@ +#pragma once + +// +// Tego Tor Configuration +// + +struct tego_tor_launch_config +{ + std::string dataDirectory; +}; + +typedef enum +{ + tego_proxy_type_none = 0, + tego_proxy_type_socks4, + tego_proxy_type_socks5, + tego_proxy_type_https, +} tego_proxy_type_t; + +struct tego_tor_daemon_config +{ + std::optional disableNetwork; + struct + { + tego_proxy_type_t type = tego_proxy_type_none; + std::string address; + uint16_t port; + std::string username; + std::string password; + } proxy; + std::vector allowedPorts; + std::vector bridges; +}; \ No newline at end of file diff --git a/src/tor/AddOnionCommand.cpp b/src/libtego/source/tor/AddOnionCommand.cpp similarity index 88% rename from src/tor/AddOnionCommand.cpp rename to src/libtego/source/tor/AddOnionCommand.cpp index 53add939..8c9c9ec0 100644 --- a/src/tor/AddOnionCommand.cpp +++ b/src/libtego/source/tor/AddOnionCommand.cpp @@ -53,10 +53,10 @@ QByteArray AddOnionCommand::build() QByteArray out("ADD_ONION"); if (m_service->privateKey().isLoaded()) { - out += " RSA1024:"; - out += m_service->privateKey().encodedPrivateKey(CryptoKey::DER).toBase64(); + out += " "; + out += m_service->privateKey().encodedKeyBlob(); } else { - out += " NEW:RSA1024"; + out += " NEW:ED25519-V3"; } foreach (const HiddenService::Target &target, m_service->targets()) { @@ -80,15 +80,16 @@ void AddOnionCommand::onReply(int statusCode, const QByteArray &data) return; } - const QByteArray keyPrefix("PrivateKey=RSA1024:"); - if (data.startsWith(keyPrefix)) { - QByteArray keyData(QByteArray::fromBase64(data.mid(keyPrefix.size()))); + const char PRIVATE_KEY_EQUALS[] = "PrivateKey="; + + if(data.startsWith(PRIVATE_KEY_EQUALS)) + { CryptoKey key; - if (!key.loadFromData(keyData, CryptoKey::PrivateKey, CryptoKey::DER)) { + if(!key.loadFromKeyBlob(data.mid(static_cast(static_strlen(PRIVATE_KEY_EQUALS))))) + { m_errorMessage = QStringLiteral("Key decoding failed"); return; } - m_service->setPrivateKey(key); } } diff --git a/src/tor/AddOnionCommand.h b/src/libtego/source/tor/AddOnionCommand.h similarity index 92% rename from src/tor/AddOnionCommand.h rename to src/libtego/source/tor/AddOnionCommand.h index 7c0afaf5..e9f88911 100644 --- a/src/tor/AddOnionCommand.h +++ b/src/libtego/source/tor/AddOnionCommand.h @@ -34,9 +34,6 @@ #define ADDONIONCOMMAND_H #include "TorControlCommand.h" -#include -#include -#include namespace Tor { @@ -47,10 +44,6 @@ class AddOnionCommand : public TorControlCommand { Q_OBJECT Q_DISABLE_COPY(AddOnionCommand) - - Q_PROPERTY(QString errorMessage READ errorMessage CONSTANT) - Q_PROPERTY(bool successful READ isSuccessful CONSTANT) - public: AddOnionCommand(HiddenService *service); diff --git a/src/tor/AuthenticateCommand.cpp b/src/libtego/source/tor/AuthenticateCommand.cpp similarity index 100% rename from src/tor/AuthenticateCommand.cpp rename to src/libtego/source/tor/AuthenticateCommand.cpp diff --git a/src/tor/AuthenticateCommand.h b/src/libtego/source/tor/AuthenticateCommand.h similarity index 100% rename from src/tor/AuthenticateCommand.h rename to src/libtego/source/tor/AuthenticateCommand.h diff --git a/src/tor/GetConfCommand.cpp b/src/libtego/source/tor/GetConfCommand.cpp similarity index 99% rename from src/tor/GetConfCommand.cpp rename to src/libtego/source/tor/GetConfCommand.cpp index 77b511e5..61952196 100644 --- a/src/tor/GetConfCommand.cpp +++ b/src/libtego/source/tor/GetConfCommand.cpp @@ -32,7 +32,6 @@ #include "GetConfCommand.h" #include "utils/StringUtil.h" -#include using namespace Tor; diff --git a/src/tor/GetConfCommand.h b/src/libtego/source/tor/GetConfCommand.h similarity index 96% rename from src/tor/GetConfCommand.h rename to src/libtego/source/tor/GetConfCommand.h index 0de97d1b..c9875f35 100644 --- a/src/tor/GetConfCommand.h +++ b/src/libtego/source/tor/GetConfCommand.h @@ -34,8 +34,6 @@ #define GETCONFCOMMAND_H #include "TorControlCommand.h" -#include -#include namespace Tor { @@ -44,9 +42,6 @@ class GetConfCommand : public TorControlCommand { Q_OBJECT Q_DISABLE_COPY(GetConfCommand) - - Q_PROPERTY(QVariantMap results READ results CONSTANT) - public: enum Type { GetConf, diff --git a/src/tor/HiddenService.cpp b/src/libtego/source/tor/HiddenService.cpp similarity index 70% rename from src/tor/HiddenService.cpp rename to src/libtego/source/tor/HiddenService.cpp index 0621400e..7088860b 100644 --- a/src/tor/HiddenService.cpp +++ b/src/libtego/source/tor/HiddenService.cpp @@ -35,10 +35,6 @@ #include "TorSocket.h" #include "utils/CryptoKey.h" #include "utils/Useful.h" -#include -#include -#include -#include using namespace Tor; @@ -47,19 +43,8 @@ HiddenService::HiddenService(QObject *parent) { } -HiddenService::HiddenService(const QString &path, QObject *parent) - : QObject(parent), m_dataPath(path), m_status(NotCreated) -{ - /* Set the initial status and, if possible, load the hostname */ - if (QDir(m_dataPath).exists(QLatin1String("private_key"))) { - loadPrivateKey(); - if (!m_hostname.isEmpty()) - m_status = Offline; - } -} - -HiddenService::HiddenService(const CryptoKey &privateKey, const QString &path, QObject *parent) - : QObject(parent), m_dataPath(path), m_status(NotCreated) +HiddenService::HiddenService(const CryptoKey &privateKey, QObject *parent) + : QObject(parent), m_status(NotCreated) { setPrivateKey(privateKey); m_status = Offline; @@ -93,39 +78,22 @@ void HiddenService::addTarget(quint16 servicePort, QHostAddress targetAddress, q void HiddenService::setPrivateKey(const CryptoKey &key) { if (m_privateKey.isLoaded()) { - BUG() << "Cannot change the private key on an existing HiddenService"; + TEGO_BUG() << "Cannot change the private key on an existing HiddenService"; return; } if (!key.isPrivate()) { - BUG() << "Cannot create a hidden service with a public key"; + TEGO_BUG() << "Cannot create a hidden service with a public key"; return; } m_privateKey = key; - m_hostname = m_privateKey.torServiceID() + QStringLiteral(".onion"); - emit privateKeyChanged(); -} - -void HiddenService::loadPrivateKey() -{ - if (m_privateKey.isLoaded() || m_dataPath.isEmpty()) - return; - - bool ok = m_privateKey.loadFromFile(m_dataPath + QLatin1String("/private_key"), CryptoKey::PrivateKey); - if (!ok) { - qWarning() << "Failed to load hidden service key"; - return; - } - - m_hostname = m_privateKey.torServiceID(); + m_hostname = QString::fromUtf8(m_privateKey.torServiceID()) + QStringLiteral(".onion"); emit privateKeyChanged(); } void HiddenService::servicePublished() { - loadPrivateKey(); - if (m_hostname.isEmpty()) { qDebug() << "Failed to read hidden service hostname"; return; diff --git a/src/tor/HiddenService.h b/src/libtego/source/tor/HiddenService.h similarity index 89% rename from src/tor/HiddenService.h rename to src/libtego/source/tor/HiddenService.h index af7790e8..501674c7 100644 --- a/src/tor/HiddenService.h +++ b/src/libtego/source/tor/HiddenService.h @@ -33,9 +33,6 @@ #ifndef HIDDENSERVICE_H #define HIDDENSERVICE_H -#include -#include -#include #include "utils/CryptoKey.h" namespace Tor @@ -65,13 +62,11 @@ class HiddenService : public QObject }; HiddenService(QObject *parent = 0); - HiddenService(const QString &dataPath, QObject *parent = 0); - HiddenService(const CryptoKey &privateKey, const QString &dataPath = QString(), QObject *parent = 0); + HiddenService(const CryptoKey &privateKey, QObject *parent = 0); Status status() const { return m_status; } const QString &hostname() const { return m_hostname; } - const QString &dataPath() const { return m_dataPath; } CryptoKey privateKey() { return m_privateKey; } void setPrivateKey(const CryptoKey &privateKey); @@ -89,13 +84,11 @@ private slots: void servicePublished(); private: - QString m_dataPath; QList m_targets; QString m_hostname; Status m_status; CryptoKey m_privateKey; - void loadPrivateKey(); void setStatus(Status newStatus); }; diff --git a/src/tor/ProtocolInfoCommand.cpp b/src/libtego/source/tor/ProtocolInfoCommand.cpp similarity index 99% rename from src/tor/ProtocolInfoCommand.cpp rename to src/libtego/source/tor/ProtocolInfoCommand.cpp index 8222d802..70c61c8b 100644 --- a/src/tor/ProtocolInfoCommand.cpp +++ b/src/libtego/source/tor/ProtocolInfoCommand.cpp @@ -33,7 +33,6 @@ #include "ProtocolInfoCommand.h" #include "TorControl.h" #include "utils/StringUtil.h" -#include using namespace Tor; diff --git a/src/tor/ProtocolInfoCommand.h b/src/libtego/source/tor/ProtocolInfoCommand.h similarity index 99% rename from src/tor/ProtocolInfoCommand.h rename to src/libtego/source/tor/ProtocolInfoCommand.h index 7789cfef..c4845565 100644 --- a/src/tor/ProtocolInfoCommand.h +++ b/src/libtego/source/tor/ProtocolInfoCommand.h @@ -34,7 +34,6 @@ #define PROTOCOLINFOCOMMAND_H #include "TorControlCommand.h" -#include namespace Tor { diff --git a/src/tor/SetConfCommand.cpp b/src/libtego/source/tor/SetConfCommand.cpp similarity index 95% rename from src/tor/SetConfCommand.cpp rename to src/libtego/source/tor/SetConfCommand.cpp index eb3a13ba..4c141486 100644 --- a/src/tor/SetConfCommand.cpp +++ b/src/libtego/source/tor/SetConfCommand.cpp @@ -33,6 +33,9 @@ #include "SetConfCommand.h" #include "utils/StringUtil.h" +#include "globals.hpp" +using tego::g_globals; + using namespace Tor; SetConfCommand::SetConfCommand() @@ -102,5 +105,7 @@ void SetConfCommand::onFinished(int statusCode) emit setConfSucceeded(); else emit setConfFailed(statusCode); + + g_globals.context->callback_registry_.emit_update_tor_daemon_config_succeeded(isSuccessful() ? TEGO_TRUE : TEGO_FALSE); } diff --git a/src/tor/SetConfCommand.h b/src/libtego/source/tor/SetConfCommand.h similarity index 93% rename from src/tor/SetConfCommand.h rename to src/libtego/source/tor/SetConfCommand.h index 5bdcb932..96646639 100644 --- a/src/tor/SetConfCommand.h +++ b/src/libtego/source/tor/SetConfCommand.h @@ -34,9 +34,6 @@ #define SETCONFCOMMAND_H #include "TorControlCommand.h" -#include -#include -#include namespace Tor { @@ -45,10 +42,6 @@ class SetConfCommand : public TorControlCommand { Q_OBJECT Q_DISABLE_COPY(SetConfCommand) - - Q_PROPERTY(QString errorMessage READ errorMessage CONSTANT) - Q_PROPERTY(bool successful READ isSuccessful CONSTANT) - public: SetConfCommand(); diff --git a/src/tor/TorControl.cpp b/src/libtego/source/tor/TorControl.cpp similarity index 84% rename from src/tor/TorControl.cpp rename to src/libtego/source/tor/TorControl.cpp index d5a70912..35454a09 100644 --- a/src/tor/TorControl.cpp +++ b/src/libtego/source/tor/TorControl.cpp @@ -30,6 +30,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include "utils/PendingOperation.h" #include "TorControl.h" #include "TorControlSocket.h" #include "HiddenService.h" @@ -39,16 +40,11 @@ #include "GetConfCommand.h" #include "AddOnionCommand.h" #include "utils/StringUtil.h" -#include "utils/Settings.h" -#include "utils/PendingOperation.h" -#include -#include -#include -#include -#include -#include -#include -#include + +#include "error.hpp" +#include "globals.hpp" +#include "signals.hpp" +using tego::g_globals; Tor::TorControl *torControl = 0; @@ -135,6 +131,9 @@ void TorControlPrivate::setStatus(TorControl::Status n) emit q->statusChanged(status, old); + g_globals.context->callback_registry_.emit_tor_control_status_changed( + static_cast(status)); + if (status == TorControl::Connected && old < TorControl::Connected) emit q->connected(); else if (status < TorControl::Connected && old >= TorControl::Connected) @@ -144,16 +143,40 @@ void TorControlPrivate::setStatus(TorControl::Status n) void TorControlPrivate::setTorStatus(TorControl::TorStatus n) { if (n == torStatus) + { return; + } TorControl::TorStatus old = torStatus; torStatus = n; emit q->torStatusChanged(torStatus, old); emit q->connectivityChanged(); - if (torStatus == TorControl::TorReady && socksAddress.isNull()) { - // Request info again to read the SOCKS port - getTorInfo(); + switch(torStatus) + { + case TorControl::TorUnknown: + g_globals.context->callback_registry_.emit_tor_network_status_changed(tego_tor_network_status_unknown); + break; + case TorControl::TorOffline: + g_globals.context->callback_registry_.emit_tor_network_status_changed(tego_tor_network_status_offline); + break; + case TorControl::TorReady: + g_globals.context->callback_registry_.emit_tor_network_status_changed(tego_tor_network_status_ready); + break; + } + + + if (torStatus == TorControl::TorReady) +{ + if (socksAddress.isNull()) + { + // Request info again to read the SOCKS port + getTorInfo(); + } + else + { + g_globals.context->set_host_user_state(tego_host_user_state_online); + } } } @@ -164,6 +187,12 @@ void TorControlPrivate::setError(const QString &message) qWarning() << "torctrl: Error:" << errorMessage; + auto tegoError = std::make_unique(); + tegoError->message = message.toStdString(); + g_globals.context->callback_registry_.emit_tor_error_occurred( + tego_tor_error_origin_control, + tegoError.release()); + socket->abort(); QTimer::singleShot(15000, q, SLOT(reconnect())); @@ -398,18 +427,7 @@ void TorControlPrivate::getTorInfo() QList keys; keys << QByteArray("status/circuit-established") << QByteArray("status/bootstrap-phase"); - /* If these are set in the config, they override the automatic behavior. */ - SettingsObject settings(QStringLiteral("tor")); - QHostAddress forceAddress(settings.read("socksAddress").toString()); - quint16 port = (quint16)settings.read("socksPort").toInt(); - - if (!forceAddress.isNull() && port) { - qDebug() << "torctrl: Using manually specified SOCKS connection settings"; - socksAddress = forceAddress; - socksPort = port; - emit q->connectivityChanged(); - } else - keys << QByteArray("net/listeners/socks"); + keys << QByteArray("net/listeners/socks"); socket->sendCommand(command, command->build(keys)); } @@ -425,7 +443,7 @@ void TorControlPrivate::getTorInfoReply() QByteArray value = unquotedString(*it); int sepp = value.indexOf(':'); QHostAddress address(QString::fromLatin1(value.mid(0, sepp))); - quint16 port = (quint16)value.mid(sepp+1).toUInt(); + quint16 port = static_cast(value.mid(sepp+1).toUInt()); /* Use the first address that matches the one used for this control connection. If none do, * just use the first address and rely on the user to reconfigure if necessary (not a problem; @@ -448,6 +466,7 @@ void TorControlPrivate::getTorInfoReply() if (command->get(QByteArray("status/circuit-established")).toInt() == 1) { qDebug() << "torctrl: Tor indicates that circuits have been established; state is TorReady"; + g_globals.context->set_host_user_state(tego_host_user_state_online); setTorStatus(TorControl::TorReady); } else { setTorStatus(TorControl::TorOffline); @@ -472,63 +491,18 @@ void TorControlPrivate::publishServices() if (services.isEmpty()) return; - SettingsObject settings(QStringLiteral("tor")); - if (settings.read("neverPublishServices").toBool()) - { - qDebug() << "torctrl: Skipping service publication because neverPublishService is enabled"; - - /* Call servicePublished under the assumption that they're published externally. */ - for (QList::Iterator it = services.begin(); it != services.end(); ++it) - (*it)->servicePublished(); - - return; - } - - if (q->torVersionAsNewAs(QStringLiteral("0.2.7"))) { - foreach (HiddenService *service, services) { - if (service->hostname().isEmpty()) - qDebug() << "torctrl: Creating a new hidden service"; - else - qDebug() << "torctrl: Publishing hidden service" << service->hostname(); - AddOnionCommand *onionCommand = new AddOnionCommand(service); - QObject::connect(onionCommand, &AddOnionCommand::succeeded, service, &HiddenService::servicePublished); - socket->sendCommand(onionCommand, onionCommand->build()); - } - } else { - qDebug() << "torctrl: Using legacy SETCONF hidden service configuration for tor" << torVersion; - SetConfCommand *command = new SetConfCommand; - QList > torConfig; - - foreach (HiddenService *service, services) - { - if (service->dataPath().isEmpty()) - continue; - - if (service->privateKey().isLoaded() && !QFile::exists(service->dataPath() + QStringLiteral("/private_key"))) { - // This case can happen if tor is downgraded after the profile is created - qWarning() << "Cannot publish ephemeral hidden services with this version of tor; skipping"; - continue; - } - - qDebug() << "torctrl: Configuring hidden service at" << service->dataPath(); - - QDir dir(service->dataPath()); - torConfig.append(qMakePair(QByteArray("HiddenServiceDir"), dir.absolutePath().toLocal8Bit())); - - const QList &targets = service->targets(); - for (QList::ConstIterator tit = targets.begin(); tit != targets.end(); ++tit) - { - QString target = QString::fromLatin1("%1 %2:%3").arg(tit->servicePort) - .arg(tit->targetAddress.toString()) - .arg(tit->targetPort); - torConfig.append(qMakePair(QByteArray("HiddenServicePort"), target.toLatin1())); - } + // v3 works in all supported tor versions: + // https://trac.torproject.org/projects/tor/wiki/org/teams/NetworkTeam/CoreTorReleases + Q_ASSERT(q->torVersionAsNewAs(QStringLiteral("0.3.5"))); - QObject::connect(command, &SetConfCommand::setConfSucceeded, service, &HiddenService::servicePublished); - } - - if (!torConfig.isEmpty()) - socket->sendCommand(command, command->build(torConfig)); + foreach (HiddenService *service, services) { + if (service->hostname().isEmpty()) + qDebug() << "torctrl: Creating a new hidden service"; + else + qDebug() << "torctrl: Publishing hidden service" << service->hostname(); + AddOnionCommand *onionCommand = new AddOnionCommand(service); + QObject::connect(onionCommand, &AddOnionCommand::succeeded, service, &HiddenService::servicePublished); + socket->sendCommand(onionCommand, onionCommand->build()); } } @@ -591,6 +565,15 @@ void TorControlPrivate::updateBootstrap(const QList &data) bootstrapStatus[key.toLower()] = value; } + // these functions just access 'bootstrapStatus' and parse out the relevant keys + // a bit roundabout but better than duplicating the tag parsing logic + auto progress = g_globals.context->get_tor_bootstrap_progress(); + auto tag = g_globals.context->get_tor_bootstrap_tag(); + + g_globals.context->callback_registry_.emit_tor_bootstrap_status_changed( + progress, + tag); + qDebug() << bootstrapStatus; emit q->bootstrapStatusChanged(); } diff --git a/src/tor/TorControl.h b/src/libtego/source/tor/TorControl.h similarity index 78% rename from src/tor/TorControl.h rename to src/libtego/source/tor/TorControl.h index 23b9b718..21b20c68 100644 --- a/src/tor/TorControl.h +++ b/src/libtego/source/tor/TorControl.h @@ -33,8 +33,6 @@ #ifndef TORCONTROL_H #define TORCONTROL_H -#include -#include #include "utils/PendingOperation.h" class QNetworkProxy; @@ -48,20 +46,8 @@ class TorControlPrivate; class TorControl : public QObject { Q_OBJECT - Q_ENUMS(Status TorStatus) - - // Status of the control connection - Q_PROPERTY(Status status READ status NOTIFY statusChanged) - // Status of Tor (and whether it believes it can connect) - Q_PROPERTY(TorStatus torStatus READ torStatus NOTIFY torStatusChanged) - // Whether it's possible to make a SOCKS connection and connect - Q_PROPERTY(bool hasConnectivity READ hasConnectivity NOTIFY connectivityChanged) - Q_PROPERTY(QString torVersion READ torVersion NOTIFY connected) - Q_PROPERTY(QString errorMessage READ errorMessage NOTIFY statusChanged) - Q_PROPERTY(QVariantMap bootstrapStatus READ bootstrapStatus NOTIFY bootstrapStatusChanged) - Q_PROPERTY(bool hasOwnership READ hasOwnership NOTIFY hasOwnershipChanged) - public: + // control status enum Status { Error = -1, @@ -71,6 +57,7 @@ class TorControl : public QObject Connected }; + // daemon status enum TorStatus { TorUnknown, @@ -109,9 +96,9 @@ class TorControl : public QObject void addHiddenService(HiddenService *service); QVariantMap bootstrapStatus() const; - Q_INVOKABLE QObject *getConfiguration(const QString &options); - Q_INVOKABLE QObject *setConfiguration(const QVariantMap &options); - Q_INVOKABLE PendingOperation *saveConfiguration(); + QObject *getConfiguration(const QString &options); + QObject *setConfiguration(const QVariantMap &options); + PendingOperation *saveConfiguration(); signals: void statusChanged(int newStatus, int oldStatus); @@ -135,7 +122,4 @@ public slots: }; } - -extern Tor::TorControl *torControl; - #endif // TORCONTROLMANAGER_H diff --git a/src/tor/TorControlCommand.cpp b/src/libtego/source/tor/TorControlCommand.cpp similarity index 99% rename from src/tor/TorControlCommand.cpp rename to src/libtego/source/tor/TorControlCommand.cpp index 48d4aab8..1fe08113 100644 --- a/src/tor/TorControlCommand.cpp +++ b/src/libtego/source/tor/TorControlCommand.cpp @@ -31,7 +31,6 @@ */ #include "TorControlCommand.h" -#include using namespace Tor; diff --git a/src/tor/TorControlCommand.h b/src/libtego/source/tor/TorControlCommand.h similarity index 98% rename from src/tor/TorControlCommand.h rename to src/libtego/source/tor/TorControlCommand.h index 89438105..6fb9f76f 100644 --- a/src/tor/TorControlCommand.h +++ b/src/libtego/source/tor/TorControlCommand.h @@ -33,9 +33,6 @@ #ifndef TORCONTROLCOMMAND_H #define TORCONTROLCOMMAND_H -#include -#include - namespace Tor { diff --git a/src/tor/TorControlSocket.cpp b/src/libtego/source/tor/TorControlSocket.cpp similarity index 99% rename from src/tor/TorControlSocket.cpp rename to src/libtego/source/tor/TorControlSocket.cpp index 33b411c5..23cb306a 100644 --- a/src/tor/TorControlSocket.cpp +++ b/src/libtego/source/tor/TorControlSocket.cpp @@ -32,7 +32,6 @@ #include "TorControlSocket.h" #include "TorControlCommand.h" -#include using namespace Tor; diff --git a/src/tor/TorControlSocket.h b/src/libtego/source/tor/TorControlSocket.h similarity index 98% rename from src/tor/TorControlSocket.h rename to src/libtego/source/tor/TorControlSocket.h index 2db91150..af62b2e6 100644 --- a/src/tor/TorControlSocket.h +++ b/src/libtego/source/tor/TorControlSocket.h @@ -33,9 +33,6 @@ #ifndef TORCONTROLSOCKET_H #define TORCONTROLSOCKET_H -#include -#include - namespace Tor { diff --git a/src/tor/TorManager.cpp b/src/libtego/source/tor/TorManager.cpp similarity index 64% rename from src/tor/TorManager.cpp rename to src/libtego/source/tor/TorManager.cpp index 27f814c7..d68d34d7 100644 --- a/src/tor/TorManager.cpp +++ b/src/libtego/source/tor/TorManager.cpp @@ -34,10 +34,11 @@ #include "TorProcess.h" #include "TorControl.h" #include "GetConfCommand.h" -#include "utils/Settings.h" -#include -#include -#include + +#include "error.hpp" +#include "signals.hpp" +#include "globals.hpp" +using tego::g_globals; using namespace Tor; @@ -130,6 +131,19 @@ QStringList TorManager::logMessages() const return d->logMessages; } +QString TorManager::running() const +{ + if (d->process) + { + if (d->process->state() == TorProcess::Ready) + { + return "Yes"; + } + return "No"; + } + return "External"; +} + bool TorManager::hasError() const { return !d->errorMessage.isEmpty(); @@ -145,79 +159,49 @@ void TorManager::start() if (!d->errorMessage.isEmpty()) { d->errorMessage.clear(); emit errorChanged(); - } - - SettingsObject settings(QStringLiteral("tor")); - // If a control port is defined by config or environment, skip launching tor - if (!settings.read("controlPort").isUndefined() || - !qEnvironmentVariableIsEmpty("TOR_CONTROL_PORT")) - { - QHostAddress address(settings.read("controlAddress").toString()); - quint16 port = (quint16)settings.read("controlPort").toInt(); - QByteArray password = settings.read("controlPassword").toString().toLatin1(); - - if (!qEnvironmentVariableIsEmpty("TOR_CONTROL_HOST")) - address = QHostAddress(QString::fromLatin1(qgetenv("TOR_CONTROL_HOST"))); - - if (!qEnvironmentVariableIsEmpty("TOR_CONTROL_PORT")) { - bool ok = false; - port = qgetenv("TOR_CONTROL_PORT").toUShort(&ok); - if (!ok) - port = 0; - } - - if (!qEnvironmentVariableIsEmpty("TOR_CONTROL_PASSWD")) - password = qgetenv("TOR_CONTROL_PASSWD"); - - if (!port) { - d->setError(QStringLiteral("Invalid control port settings from environment or configuration")); - return; - } - - if (address.isNull()) - address = QHostAddress::LocalHost; - - d->control->setAuthPassword(password); - d->control->connect(address, port); - } else { - // Launch a bundled Tor instance - QString executable = d->torExecutablePath(); - if (executable.isEmpty()) { - d->setError(QStringLiteral("Cannot find tor executable")); - return; - } + auto tegoError = std::make_unique(); + g_globals.context->callback_registry_.emit_tor_error_occurred( + tego_tor_error_origin_manager, + tegoError.release()); + } - if (!d->process) { - d->process = new TorProcess(this); - connect(d->process, SIGNAL(stateChanged(int)), d, SLOT(processStateChanged(int))); - connect(d->process, SIGNAL(errorMessageChanged(QString)), d, - SLOT(processErrorChanged(QString))); - connect(d->process, SIGNAL(logMessage(QString)), d, SLOT(processLogMessage(QString))); - } + // Launch a bundled Tor instance + QString executable = d->torExecutablePath(); + if (executable.isEmpty()) { + d->setError(QStringLiteral("Cannot find tor executable")); + return; + } - if (!QFile::exists(d->dataDir) && !d->createDataDir(d->dataDir)) { - d->setError(QStringLiteral("Cannot write data location: %1").arg(d->dataDir)); - return; - } + if (!d->process) { + d->process = new TorProcess(this); + connect(d->process, SIGNAL(stateChanged(int)), d, SLOT(processStateChanged(int))); + connect(d->process, SIGNAL(errorMessageChanged(QString)), d, + SLOT(processErrorChanged(QString))); + connect(d->process, SIGNAL(logMessage(QString)), d, SLOT(processLogMessage(QString))); + } - QString defaultTorrc = d->dataDir + QStringLiteral("default_torrc"); - if (!QFile::exists(defaultTorrc) && !d->createDefaultTorrc(defaultTorrc)) { - d->setError(QStringLiteral("Cannot write data files: %1").arg(defaultTorrc)); - return; - } + if (!QFile::exists(d->dataDir) && !d->createDataDir(d->dataDir)) { + d->setError(QStringLiteral("Cannot write data location: %1").arg(d->dataDir)); + return; + } - QFile torrc(d->dataDir + QStringLiteral("torrc")); - if (!torrc.exists() || torrc.size() == 0) { - d->configNeeded = true; - emit configurationNeededChanged(); - } + QString defaultTorrc = d->dataDir + QStringLiteral("default_torrc"); + if (!QFile::exists(defaultTorrc) && !d->createDefaultTorrc(defaultTorrc)) { + d->setError(QStringLiteral("Cannot write data files: %1").arg(defaultTorrc)); + return; + } - d->process->setExecutable(executable); - d->process->setDataDir(d->dataDir); - d->process->setDefaultTorrc(defaultTorrc); - d->process->start(); + QFile torrc(d->dataDir + QStringLiteral("torrc")); + if (!torrc.exists() || torrc.size() == 0) { + d->configNeeded = true; + emit configurationNeededChanged(); } + + d->process->setExecutable(executable); + d->process->setDataDir(d->dataDir); + d->process->setDefaultTorrc(defaultTorrc); + d->process->start(); } void TorManagerPrivate::processStateChanged(int state) @@ -227,12 +211,30 @@ void TorManagerPrivate::processStateChanged(int state) control->setAuthPassword(process->controlPassword()); control->connect(process->controlHost(), process->controlPort()); } + + switch(state) + { + case TorProcess::NotStarted: + logger::trace(); + g_globals.context->callback_registry_.emit_tor_process_status_changed(tego_tor_process_status_not_started); + break; + case TorProcess::Starting: + logger::trace(); + g_globals.context->callback_registry_.emit_tor_process_status_changed(tego_tor_process_status_starting); + break; + case TorProcess::Ready: + logger::trace(); + g_globals.context->callback_registry_.emit_tor_process_status_changed(tego_tor_process_status_running); + break; + } + + emit q->runningChanged(); } -void TorManagerPrivate::processErrorChanged(const QString &errorMessage) +void TorManagerPrivate::processErrorChanged(const QString &message) { - qDebug() << "tor error:" << errorMessage; - setError(errorMessage); + qDebug() << "tor error:" << message; + setError(message); } void TorManagerPrivate::processLogMessage(const QString &message) @@ -241,6 +243,23 @@ void TorManagerPrivate::processLogMessage(const QString &message) if (logMessages.size() >= 50) logMessages.takeFirst(); logMessages.append(message); + + emit q->logMessage(message); + + // marshall message out of the QString into our own char buffer + auto utf8 = message.toUtf8(); + + const auto msgLength = utf8.size(); + const auto msgSize = msgLength + 1; + auto msg = std::make_unique(static_cast(msgSize)); + + // copy utf8 string to our own buffer + std::copy(utf8.begin(), utf8.end(), msg.get()); + Q_ASSERT(msg[static_cast(msgLength)] == 0); + + g_globals.context->callback_registry_.emit_tor_log_received( + msg.release(), + static_cast(msgLength)); } void TorManagerPrivate::controlStatusChanged(int status) @@ -273,18 +292,13 @@ void TorManagerPrivate::getConfFinished() QString TorManagerPrivate::torExecutablePath() const { - SettingsObject settings(QStringLiteral("tor")); - QString path = settings.read("executablePath").toString(); - if (!path.isEmpty()) - return path; - #ifdef Q_OS_WIN QString filename(QStringLiteral("/tor.exe")); #else QString filename(QStringLiteral("/tor")); #endif - path = qApp->applicationDirPath(); + QString path = qApp->applicationDirPath(); if (QFile::exists(path + filename)) return path + filename; @@ -324,6 +338,13 @@ void TorManagerPrivate::setError(const QString &message) { errorMessage = message; emit q->errorChanged(); + + auto tegoError = std::make_unique(); + tegoError->message = message.toStdString(); + + g_globals.context->callback_registry_.emit_tor_error_occurred( + tego_tor_error_origin_manager, + tegoError.release()); } #include "TorManager.moc" diff --git a/src/tor/TorManager.h b/src/libtego/source/tor/TorManager.h similarity index 82% rename from src/tor/TorManager.h rename to src/libtego/source/tor/TorManager.h index 1de51336..75c8f67a 100644 --- a/src/tor/TorManager.h +++ b/src/libtego/source/tor/TorManager.h @@ -33,9 +33,6 @@ #ifndef TORMANAGER_H #define TORMANAGER_H -#include -#include - namespace Tor { @@ -48,15 +45,6 @@ class TorManagerPrivate; class TorManager : public QObject { Q_OBJECT - - Q_PROPERTY(bool configurationNeeded READ configurationNeeded NOTIFY configurationNeededChanged) - Q_PROPERTY(QStringList logMessages READ logMessages CONSTANT) - Q_PROPERTY(Tor::TorProcess* process READ process CONSTANT) - Q_PROPERTY(Tor::TorControl* control READ control CONSTANT) - Q_PROPERTY(bool hasError READ hasError NOTIFY errorChanged) - Q_PROPERTY(QString errorMessage READ errorMessage NOTIFY errorChanged) - Q_PROPERTY(QString dataDirectory READ dataDirectory WRITE setDataDirectory) - public: explicit TorManager(QObject *parent = 0); static TorManager *instance(); @@ -71,16 +59,18 @@ class TorManager : public QObject bool configurationNeeded() const; QStringList logMessages() const; + QString running() const; bool hasError() const; QString errorMessage() const; - public slots: void start(); signals: void configurationNeededChanged(); void errorChanged(); + void logMessage(const QString &message); + void runningChanged(); private: TorManagerPrivate *d; diff --git a/src/tor/TorProcess.cpp b/src/libtego/source/tor/TorProcess.cpp similarity index 94% rename from src/tor/TorProcess.cpp rename to src/libtego/source/tor/TorProcess.cpp index 06015782..7904fe0b 100644 --- a/src/tor/TorProcess.cpp +++ b/src/libtego/source/tor/TorProcess.cpp @@ -33,9 +33,6 @@ #include "TorProcess_p.h" #include "utils/CryptoKey.h" #include "utils/SecureRNG.h" -#include -#include -#include using namespace Tor; @@ -50,14 +47,13 @@ TorProcess::~TorProcess() stop(); } -TorProcessPrivate::TorProcessPrivate(TorProcess *q) - : QObject(q), q(q), state(TorProcess::NotStarted), controlPort(0), controlPortAttempts(0) +TorProcessPrivate::TorProcessPrivate(TorProcess *tp) + : QObject(tp), q(tp), state(TorProcess::NotStarted), controlPort(0), controlPortAttempts(0) { connect(&process, &QProcess::started, this, &TorProcessPrivate::processStarted); - connect(&process, (void (QProcess::*)(int, QProcess::ExitStatus))&QProcess::finished, - this, &TorProcessPrivate::processFinished); - connect(&process, (void (QProcess::*)(QProcess::ProcessError))&QProcess::error, - this, &TorProcessPrivate::processError); + //XXX: This static cast shouldn't be needed here, but it is. Why? + connect(&process, static_cast(&QProcess::finished), this, &TorProcessPrivate::processFinished); + connect(&process, &QProcess::errorOccurred, this, &TorProcessPrivate::processError); connect(&process, &QProcess::readyRead, this, &TorProcessPrivate::processReadable); controlPortTimer.setInterval(500); diff --git a/src/tor/TorProcess.h b/src/libtego/source/tor/TorProcess.h similarity index 93% rename from src/tor/TorProcess.h rename to src/libtego/source/tor/TorProcess.h index ad489dc4..75c2c9c1 100644 --- a/src/tor/TorProcess.h +++ b/src/libtego/source/tor/TorProcess.h @@ -33,9 +33,6 @@ #ifndef TORPROCESS_H #define TORPROCESS_H -#include -#include - namespace Tor { @@ -46,11 +43,6 @@ class TorProcessPrivate; class TorProcess : public QObject { Q_OBJECT - Q_ENUMS(State) - - Q_PROPERTY(State state READ state NOTIFY stateChanged) - Q_PROPERTY(QString errorMessage READ errorMessage NOTIFY errorMessageChanged) - public: enum State { Failed = -1, diff --git a/src/tor/TorProcess_p.h b/src/libtego/source/tor/TorProcess_p.h similarity index 98% rename from src/tor/TorProcess_p.h rename to src/libtego/source/tor/TorProcess_p.h index 9aa2585e..60dc5e2d 100644 --- a/src/tor/TorProcess_p.h +++ b/src/libtego/source/tor/TorProcess_p.h @@ -34,8 +34,6 @@ #define TORPROCESS_P_H #include "TorProcess.h" -#include -#include namespace Tor { diff --git a/src/tor/TorSocket.cpp b/src/libtego/source/tor/TorSocket.cpp similarity index 88% rename from src/tor/TorSocket.cpp rename to src/libtego/source/tor/TorSocket.cpp index 615369b3..63832e02 100644 --- a/src/tor/TorSocket.cpp +++ b/src/libtego/source/tor/TorSocket.cpp @@ -30,9 +30,11 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include "globals.hpp" +using tego::g_globals; + #include "TorSocket.h" #include "TorControl.h" -#include using namespace Tor; @@ -43,7 +45,7 @@ TorSocket::TorSocket(QObject *parent) , m_maxInterval(900) , m_connectAttempts(0) { - connect(torControl, SIGNAL(connectivityChanged()), SLOT(connectivityChanged())); + connect(g_globals.context->torControl, SIGNAL(connectivityChanged()), SLOT(connectivityChanged())); connect(&m_connectTimer, SIGNAL(timeout()), SLOT(reconnect())); connect(this, SIGNAL(disconnected()), SLOT(onFailed())); connect(this, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(onFailed())); @@ -99,7 +101,7 @@ int TorSocket::reconnectInterval() void TorSocket::reconnect() { - if (!torControl->hasConnectivity() || !reconnectEnabled()) + if (!g_globals.context->torControl->hasConnectivity() || !reconnectEnabled()) return; m_connectTimer.stop(); @@ -111,8 +113,8 @@ void TorSocket::reconnect() void TorSocket::connectivityChanged() { - if (torControl->hasConnectivity()) { - setProxy(torControl->connectionProxy()); + if (g_globals.context->torControl->hasConnectivity()) { + setProxy(g_globals.context->torControl->connectionProxy()); if (state() == QAbstractSocket::UnconnectedState) reconnect(); } else { @@ -127,11 +129,11 @@ void TorSocket::connectToHost(const QString &hostName, quint16 port, OpenMode op m_host = hostName; m_port = port; - if (!torControl->hasConnectivity()) + if (!g_globals.context->torControl->hasConnectivity()) return; - if (proxy() != torControl->connectionProxy()) - setProxy(torControl->connectionProxy()); + if (proxy() != g_globals.context->torControl->connectionProxy()) + setProxy(g_globals.context->torControl->connectionProxy()); QAbstractSocket::connectToHost(hostName, port, openMode, protocol); } diff --git a/src/tor/TorSocket.h b/src/libtego/source/tor/TorSocket.h similarity index 98% rename from src/tor/TorSocket.h rename to src/libtego/source/tor/TorSocket.h index 0c68f785..066ea04d 100644 --- a/src/tor/TorSocket.h +++ b/src/libtego/source/tor/TorSocket.h @@ -33,9 +33,6 @@ #ifndef TORSOCKET_H #define TORSOCKET_H -#include -#include - namespace Tor { /* Specialized QTcpSocket which makes connections over the SOCKS proxy diff --git a/src/libtego/source/tor_stubs.cpp b/src/libtego/source/tor_stubs.cpp new file mode 100644 index 00000000..4c2afbed --- /dev/null +++ b/src/libtego/source/tor_stubs.cpp @@ -0,0 +1,81 @@ +#include "error.hpp" + +#define NOT_USED(...) TEGO_THROW_MSG("{} should never be called", __FUNCTION__) + +extern "C" +{ + void *tor_malloc_(size_t size) + { + return std::malloc(size); + } + + void *tor_malloc_zero_(size_t size) + { + void* retval = tor_malloc_(size); + if (retval) { + std::memset(retval, 0x00, size); + } + return retval; + } + + // convert assert to thrown exception + void tor_assertion_failed_( + const char *fname, + unsigned int line, + const char *func, + const char *expr, + const char *fmt, + ...) + { + (void)func; + (void)fmt; + throw std::runtime_error( + fmt::format("tor assertion failed {}:{} : {}", fname, line, expr)); + } + + // no-op, called as part of tor_assertion macro which + // ultimately goes to tor_assertion_failed which throws an exception + void tor_abort_(void) + { + throw std::runtime_error(__FUNCTION__); + } + + // no-op swallow logging calls + void log_fn_(int severity, log_domain_mask_t domain, const char *fn, + const char *format, ...) + { + (void)severity; + (void)domain; + (void)fn; + (void)format; + } + +#ifdef _WIN32 + const char* tor_fix_source_file(const char* fname) + { + return fname; + } +#endif + + void crypto_strongest_rand(uint8_t*, size_t) + { + NOT_USED(); + } + + void memwipe(void*, uint8_t, size_t) + { + NOT_USED(); + } + + size_t crypto_digest_algorithm_get_length(digest_algorithm_t) + { + NOT_USED(); + return {}; + } + + void* tor_memdup_(const void*, size_t) + { + NOT_USED(); + return {}; + } +} diff --git a/src/libtego/source/user.cpp b/src/libtego/source/user.cpp new file mode 100644 index 00000000..c0a902ac --- /dev/null +++ b/src/libtego/source/user.cpp @@ -0,0 +1,56 @@ +#include "error.hpp" +#include "user.hpp" + +tego_user_id::tego_user_id(const tego_v3_onion_service_id_t& onionServiceId) +: serviceId(onionServiceId) +{ } + +extern "C" +{ + void tego_user_id_copy( + tego_user_id_t const* userId, + tego_user_id_t** out_userId, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(userId); + TEGO_THROW_IF_NULL(out_userId); + + auto retval = std::make_unique(*userId); + *out_userId = retval.release(); + }, error); + } + + void tego_user_id_from_v3_onion_service_id( + tego_user_id_t** out_userId, + const tego_v3_onion_service_id_t* serviceId, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(out_userId); + TEGO_THROW_IF_FALSE(*out_userId == nullptr); + TEGO_THROW_IF_NULL(serviceId); + + auto userId = std::make_unique(*serviceId); + *out_userId = userId.release(); + }, error); + } + + void tego_user_id_get_v3_onion_service_id( + const tego_user_id_t* userId, + tego_v3_onion_service_id_t** out_serviceId, + tego_error_t** error) + { + return tego::translateExceptions([=]() -> void + { + TEGO_THROW_IF_NULL(userId); + TEGO_THROW_IF_NULL(out_serviceId); + TEGO_THROW_IF_FALSE(*out_serviceId == nullptr); + + auto serviceId = std::make_unique(userId->serviceId); + *out_serviceId = serviceId.release(); + }, error); + } +} diff --git a/src/libtego/source/user.hpp b/src/libtego/source/user.hpp new file mode 100644 index 00000000..2c231bc7 --- /dev/null +++ b/src/libtego/source/user.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "ed25519.hpp" + +struct tego_user_id +{ + tego_user_id(const tego_v3_onion_service_id_t&); + tego_user_id(const tego_user_id&) = default; + + tego_v3_onion_service_id_t serviceId; +}; \ No newline at end of file diff --git a/src/libtego/source/utils/CryptoKey.cpp b/src/libtego/source/utils/CryptoKey.cpp new file mode 100644 index 00000000..326166b1 --- /dev/null +++ b/src/libtego/source/utils/CryptoKey.cpp @@ -0,0 +1,194 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "CryptoKey.h" +#include "SecureRNG.h" +#include "Useful.h" +#include "utils/StringUtil.h" + +bool CryptoKey::loadFromServiceId(const QByteArray& data) +{ + this->clear(); + // convert string to service id + std::unique_ptr serviceId; + tego_v3_onion_service_id_from_string( + tego::out(serviceId), + data.data(), + static_cast(data.size()), + tego::throw_on_error()); + + // extract public key from service id + std::unique_ptr publicKey; + tego_ed25519_public_key_from_v3_onion_service_id( + tego::out(publicKey), + serviceId.get(), + tego::throw_on_error()); + this->publicKey_ = std::move(publicKey); + + return true; +} + +bool CryptoKey::loadFromKeyBlob(const QByteArray& keyBlob) +{ + this->clear(); + + // convert keyblob to private key + std::unique_ptr privateKey; + tego_ed25519_private_key_from_ed25519_keyblob( + tego::out(privateKey), + keyBlob.data(), + static_cast(keyBlob.size()), + tego::throw_on_error()); + this->privateKey_ = std::move(privateKey); + + // claculate public key from private + std::unique_ptr publicKey; + tego_ed25519_public_key_from_ed25519_private_key( + tego::out(publicKey), + this->privateKey_.get(), + tego::throw_on_error()); + this->publicKey_ = std::move(publicKey); + + return true; +} + +void CryptoKey::clear() +{ + privateKey_ = {}; + publicKey_ = {}; +} + +bool CryptoKey::isPrivate() const +{ + return privateKey_.get() != nullptr; +} + +QByteArray CryptoKey::encodedKeyBlob() const +{ + // encode private key to KeyBlob + char keyBlob[TEGO_ED25519_KEYBLOB_SIZE] = {0}; + tego_ed25519_keyblob_from_ed25519_private_key(keyBlob, sizeof(keyBlob), this->privateKey_.get(), tego::throw_on_error()); + + QByteArray retval = {keyBlob, TEGO_ED25519_KEYBLOB_LENGTH}; + return retval; +} + +QByteArray CryptoKey::torServiceID() const +{ + // convert public key to service id + std::unique_ptr serviceId; + tego_v3_onion_service_id_from_ed25519_public_key( + tego::out(serviceId), + this->publicKey_.get(), + tego::throw_on_error()); + + // service id to string + char serviceIdString[TEGO_V3_ONION_SERVICE_ID_SIZE] = {0}; + tego_v3_onion_service_id_to_string( + serviceId.get(), + serviceIdString, + sizeof(serviceIdString), + tego::throw_on_error()); + + QByteArray retval(serviceIdString); + return retval; +} + +QByteArray CryptoKey::signData(const QByteArray &msg) const +{ + // calculate signature + std::unique_ptr signature; + tego_message_ed25519_sign( + reinterpret_cast(msg.data()), + static_cast(msg.size()), + this->privateKey_.get(), + this->publicKey_.get(), + tego::out(signature), + tego::throw_on_error()); + + // signature to byte blob + QByteArray retval(TEGO_ED25519_SIGNATURE_SIZE, 0x00); + tego_ed25519_signature_to_bytes( + signature.get(), + reinterpret_cast(retval.data()), + static_cast(retval.size()), + tego::throw_on_error()); + + return retval; +} + +bool CryptoKey::verifyData(const QByteArray &msg, QByteArray signatureBytes) const +{ + // load signature from buffer + std::unique_ptr signature; + tego_ed25519_signature_from_bytes( + tego::out(signature), + reinterpret_cast(signatureBytes.data()), + static_cast(signatureBytes.size()), + tego::throw_on_error()); + + // verify it against msg and our public key + return tego_ed25519_signature_verify( + signature.get(), + reinterpret_cast(msg.data()), + static_cast(msg.size()), + this->publicKey_.get(), + tego::throw_on_error()); +} + +/* Cryptographic hash of a password as expected by Tor's HashedControlPassword */ +QByteArray torControlHashedPassword(const QByteArray &password) +{ + QByteArray salt = SecureRNG::random(8); + if (salt.isNull()) + return QByteArray(); + + int count = (16u + (96 & 15)) << ((96 >> 4) + 6); + + SHA_CTX hash; + SHA1_Init(&hash); + + QByteArray tmp = salt + password; + while (count) + { + int c = qMin(count, tmp.size()); + SHA1_Update(&hash, reinterpret_cast(tmp.constData()), static_cast(c)); + count -= c; + } + + unsigned char md[20]; + SHA1_Final(md, &hash); + + /* 60 is the hex-encoded value of 96, which is a constant used by Tor's algorithm. */ + return QByteArray("16:") + salt.toHex().toUpper() + QByteArray("60") + + QByteArray::fromRawData(reinterpret_cast(md), 20).toHex().toUpper(); +} diff --git a/src/utils/CryptoKey.h b/src/libtego/source/utils/CryptoKey.h similarity index 60% rename from src/utils/CryptoKey.h rename to src/libtego/source/utils/CryptoKey.h index 70ada197..51b29926 100644 --- a/src/utils/CryptoKey.h +++ b/src/libtego/source/utils/CryptoKey.h @@ -33,61 +33,32 @@ #ifndef CRYPTOKEY_H #define CRYPTOKEY_H -#include -#include -#include - class CryptoKey { public: - enum KeyType { - PrivateKey, - PublicKey - }; - - enum KeyFormat { - PEM, - DER - }; - - CryptoKey(); - CryptoKey(const CryptoKey &other) : d(other.d) { } - ~CryptoKey(); + // loads public key from service id + bool loadFromServiceId(const QByteArray &data); + // load private key from ed25519 KeyBlob format + bool loadFromKeyBlob(const QByteArray& keyBlob); + // load from Service Id - bool loadFromData(const QByteArray &data, KeyType type, KeyFormat format = PEM); - bool loadFromFile(const QString &path, KeyType type, KeyFormat format = PEM); void clear(); - bool isLoaded() const { return d.data() && d->key != 0; } + bool isLoaded() const { return privateKey_ != nullptr || publicKey_ != nullptr; } bool isPrivate() const; - QByteArray publicKeyDigest() const; - QByteArray encodedPublicKey(KeyFormat format) const; - QByteArray encodedPrivateKey(KeyFormat format) const; - QString torServiceID() const; - int bits() const; + // write to tor's 'KeyBlob' format + QByteArray encodedKeyBlob() const; + QByteArray torServiceID() const; - // Calculate and sign SHA-256 digest of data using this key and PKCS #1 v2.0 padding + // sign data with our private key QByteArray signData(const QByteArray &data) const; - // Verify a signature as per signData + // verify data signature against public key bool verifyData(const QByteArray &data, QByteArray signature) const; - // Sign the input SHA-256 digest using this key and PKCS #1 v2.0 padding - QByteArray signSHA256(const QByteArray &digest) const; - // Verify a signature as per signSHA256 - bool verifySHA256(const QByteArray &digest, QByteArray signature) const; - private: - struct Data : public QSharedData - { - typedef struct rsa_st RSA; - RSA *key; - - Data(RSA *k = 0) : key(k) { } - ~Data(); - }; - - QExplicitlySharedDataPointer d; + std::shared_ptr privateKey_; + std::shared_ptr publicKey_; }; QByteArray torControlHashedPassword(const QByteArray &password); diff --git a/src/utils/PendingOperation.cpp b/src/libtego/source/utils/PendingOperation.cpp similarity index 100% rename from src/utils/PendingOperation.cpp rename to src/libtego/source/utils/PendingOperation.cpp diff --git a/src/utils/PendingOperation.h b/src/libtego/source/utils/PendingOperation.h similarity index 90% rename from src/utils/PendingOperation.h rename to src/libtego/source/utils/PendingOperation.h index 8f776a8d..4f5e102f 100644 --- a/src/utils/PendingOperation.h +++ b/src/libtego/source/utils/PendingOperation.h @@ -33,8 +33,6 @@ #ifndef PENDINGOPERATION_H #define PENDINGOPERATION_H -#include - /* Represents an asynchronous operation for reporting status * * This class is used for asynchronous operations that report a @@ -51,12 +49,6 @@ class PendingOperation : public QObject { Q_OBJECT - - Q_PROPERTY(bool isFinished READ isFinished NOTIFY finished FINAL) - Q_PROPERTY(bool isSuccess READ isSuccess NOTIFY success FINAL) - Q_PROPERTY(bool isError READ isError NOTIFY error FINAL) - Q_PROPERTY(QString errorMessage READ errorMessage NOTIFY finished FINAL) - public: PendingOperation(QObject *parent = 0); diff --git a/src/utils/SecureRNG.cpp b/src/libtego/source/utils/SecureRNG.cpp similarity index 94% rename from src/utils/SecureRNG.cpp rename to src/libtego/source/utils/SecureRNG.cpp index 3a6eacd8..6d7214c7 100644 --- a/src/utils/SecureRNG.cpp +++ b/src/libtego/source/utils/SecureRNG.cpp @@ -31,18 +31,6 @@ */ #include "SecureRNG.h" -#include -#include -#include -#include - -#ifdef Q_OS_WIN -#include -#endif - -#if QT_VERSION >= 0x040700 -#include -#endif bool SecureRNG::seed() { @@ -111,7 +99,7 @@ QByteArray SecureRNG::randomPrintable(int length) { QByteArray re(length, 0); for (int i = 0; i < re.size(); i++) - re[i] = randomInt(95) + 32; + re[i] = static_cast(randomInt(95) + 32); return re; } diff --git a/src/utils/SecureRNG.h b/src/libtego/source/utils/SecureRNG.h similarity index 98% rename from src/utils/SecureRNG.h rename to src/libtego/source/utils/SecureRNG.h index f3b2a4b6..e5043913 100644 --- a/src/utils/SecureRNG.h +++ b/src/libtego/source/utils/SecureRNG.h @@ -33,8 +33,6 @@ #ifndef SECURERNG_H #define SECURERNG_H -#include - class SecureRNG { public: diff --git a/src/utils/StringUtil.cpp b/src/libtego/source/utils/StringUtil.cpp similarity index 100% rename from src/utils/StringUtil.cpp rename to src/libtego/source/utils/StringUtil.cpp diff --git a/src/utils/StringUtil.h b/src/libtego/source/utils/StringUtil.h similarity index 94% rename from src/utils/StringUtil.h rename to src/libtego/source/utils/StringUtil.h index c86d2c6e..ebff5e88 100644 --- a/src/utils/StringUtil.h +++ b/src/libtego/source/utils/StringUtil.h @@ -33,9 +33,6 @@ #ifndef STRINGUTIL_H #define STRINGUTIL_H -#include -#include - QByteArray quotedString(const QByteArray &string); /* Return the unquoted contents of a string, either until an end quote or an unescaped separator character. */ @@ -43,4 +40,11 @@ QByteArray unquotedString(const QByteArray &string); QList splitQuotedStrings(const QByteArray &input, char separator); +template +constexpr size_t static_strlen(const char (&str)[N]) +{ + Q_ASSERT(str[N-1] == 0); + return N - 1; +} + #endif // STRINGUTIL_H diff --git a/src/libtego/source/utils/Useful.h b/src/libtego/source/utils/Useful.h new file mode 100644 index 00000000..0f63d036 --- /dev/null +++ b/src/libtego/source/utils/Useful.h @@ -0,0 +1,84 @@ +/* Ricochet - https://ricochet.im/ + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef UTILS_USEFUL_H +#define UTILS_USEFUL_H + +/* Print a warning for bug conditions, and assert on a debug build. + * + * This should be used in place of Q_ASSERT for bug conditions, along + * with a proper error case for release-mode builds. For example: + * + * if (!connection || !user) { + * TEGO_BUG() << "Request" << request << "should have a connection and user"; + * return false; + * } + * + * Do not confuse bugs with actual error cases; TEGO_BUG() should never be + * triggered unless the code or logic is wrong. + */ +#if !defined(QT_NO_DEBUG) || defined(QT_FORCE_ASSERTS) +# define TEGO_BUG() Explode(__FILE__,__LINE__), qWarning() << "BUG:" +namespace { +class Explode +{ +public: + const char *file_; + int line_; + Explode(const char *file, int line) : file_(file), line_(line) { } + ~Explode() { + qt_assert("something broke!", file_, line_); + } +}; +} +#else +# define TEGO_BUG() qWarning() << "BUG:" +#endif + + +/* + * helper function for safely casting to QT sizes (generally an int) + * throws if the conversion overflows the target conversion type + */ +template +T safe_cast(F from) +{ + if (from >= std::numeric_limits::max()) + { + TEGO_BUG() << "Invalid safe_cast. Value: " << from + << "; Max value of target type: " << std::numeric_limits::max(); + } + return static_cast(from); +} + +#endif + diff --git a/src/libtego/test/CMakeLists.txt b/src/libtego/test/CMakeLists.txt new file mode 100644 index 00000000..96df7a1c --- /dev/null +++ b/src/libtego/test/CMakeLists.txt @@ -0,0 +1,38 @@ +option(ENABLE_LIBTEGO_TESTS "Build tests for libtego" OFF) + +include(lto) +include(compiler_opts) + +if (ENABLE_LIBTEGO_TESTS) + find_package(Catch2 REQUIRED) + + include(CTest) + include(Catch) + + add_library(catch_tests STATIC main.cpp) + setup_compiler(catch_tests) + + target_link_libraries(catch_tests PUBLIC Catch2::Catch2 tego) + + # add test sources here + add_executable(libtego_tests test_init.cpp) + setup_compiler(libtego_tests) + + add_test(NAME test_libtego COMMAND libtego_tests) + + target_link_libraries(libtego_tests PRIVATE catch_tests) + + catch_discover_tests( + libtego_tests + TEST_PREFIX + "unittest." + REPORTER + xml + OUTPUT_DIR + . + OUTPUT_PREFIX + "unittest." + OUTPUT_SUFFIX + ".xml") + +endif () diff --git a/src/libtego/test/main.cpp b/src/libtego/test/main.cpp new file mode 100644 index 00000000..4ed06df1 --- /dev/null +++ b/src/libtego/test/main.cpp @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN +#include diff --git a/src/libtego/test/test_init.cpp b/src/libtego/test/test_init.cpp new file mode 100644 index 00000000..dd762361 --- /dev/null +++ b/src/libtego/test/test_init.cpp @@ -0,0 +1,48 @@ +#include +#include +#include + +TEST_CASE( "Context can be created/destroyed, with valid inputs", + "[libtego][context][init][deinit][valid_input]") +{ + tego_context* context = nullptr; + + // require that the context is successfully initialized + REQUIRE_NOTHROW(tego_initialize(&context, tego::throw_on_error())); + REQUIRE(context != nullptr); + + // require that the uninitialization is successful + REQUIRE_NOTHROW(tego_uninitialize(context, tego::throw_on_error())); +} + +TEST_CASE( "Libtego refuses to create multiple contexts", + "[libtego][context][init][invalid_input]") +{ + tego_context* context = nullptr; + + // the first context should be created successfully + REQUIRE_NOTHROW(tego_initialize(&context, tego::throw_on_error())); + REQUIRE(context != nullptr); + + // the second time should throw + tego_context* context2 = nullptr; + REQUIRE_THROWS(tego_initialize(&context2, tego::throw_on_error())); + + // clean up + tego_uninitialize(context, nullptr); +} + +TEST_CASE( "Libtego refuses to create/destroy a context when passed nullptr", + "[libtego][context][init][deinit][invalid_input]") +{ + // expect tego_initialize to throw when the first param is nullptr + REQUIRE_THROWS(tego_initialize(nullptr, tego::throw_on_error())); + + // tego_uninitialize should do nothing when the first param is nullptr + REQUIRE_NOTHROW(tego_uninitialize(nullptr, tego::throw_on_error())); + + // we should still be able to successfully create and destroy contexts now + tego_context* context = nullptr; + REQUIRE_NOTHROW(tego_initialize(&context, tego::throw_on_error())); + REQUIRE_NOTHROW(tego_uninitialize(context, tego::throw_on_error())); +} diff --git a/src/libtego_ui/CMakeLists.txt b/src/libtego_ui/CMakeLists.txt new file mode 100644 index 00000000..fa99316b --- /dev/null +++ b/src/libtego_ui/CMakeLists.txt @@ -0,0 +1,161 @@ +# Ricochet Refresh - https://ricochetrefresh.net/ +# Copyright (C) 2021, Blueprint For Free Speech +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# +# * Neither the names of the copyright owners nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +cmake_minimum_required(VERSION 3.16) + +project(tego_ui LANGUAGES CXX) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if (FORCE_QT5) + find_package( + QT + NAMES + Qt5 + COMPONENTS Core + Gui + Network + Quick + Widgets + REQUIRED) +else () + find_package( + QT + NAMES + Qt6 + Qt5 + COMPONENTS Core + Gui + Network + Quick + Widgets + REQUIRED) +endif () + +find_package( + Qt${QT_VERSION_MAJOR} + COMPONENTS Core + Gui + Network + Quick + Widgets + REQUIRED) + +# Require Qt >5.15 +if (${QT_VERSION_MAJOR} EQUAL 5) + if (${QT_VERSION_MINOR} LESS 15) + message(FATAL_ERROR "Qt >5.15 is required to build this project!") + endif () +endif () + +if (APPLE) + find_package( + Qt${QT_VERSION_MAJOR} + COMPONENTS MacExtras + REQUIRED) +endif () + +add_library( + tego_ui STATIC + ui/LanguagesModel.h + ui/ContactsModel.h + ui/Clipboard.cpp + ui/Clipboard.h + ui/ContactsModel.cpp + ui/LanguagesModel.cpp + ui/MainWindow.cpp + ui/MainWindow.h + utils/Useful.h + utils/Settings.cpp + utils/Settings.h + libtego_callbacks.cpp + shims/UserIdentity.h + shims/ContactsManager.cpp + shims/TorCommand.h + shims/UserIdentity.cpp + shims/ContactUser.h + shims/ContactsManager.h + shims/OutgoingContactRequest.h + shims/ContactIDValidator.h + shims/TorControl.h + shims/TorManager.h + shims/TorCommand.cpp + shims/TorControl.cpp + shims/IncomingContactRequest.h + shims/OutgoingContactRequest.cpp + shims/ConversationModel.cpp + shims/ContactIDValidator.cpp + shims/IncomingContactRequest.cpp + shims/ContactUser.cpp + shims/ConversationModel.h + shims/TorManager.cpp + libtego_callbacks.hpp) +target_precompile_headers(tego_ui PRIVATE precomp.hpp) + +include(lto) +include(compiler_opts) +# enables compiler specific warnings/sanitizers if requested +setup_compiler(tego_ui) + +target_compile_features(tego_ui PRIVATE cxx_std_20) + +# Since ricochet-refresh includes libtego_callbacks.hpp as a system header file, we export the include directory twice, +# once as local, once as system TODO: perhaps there's a cleaner way to go about this +target_include_directories(tego_ui PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_include_directories(tego_ui SYSTEM PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +target_link_libraries(tego_ui PUBLIC tego) + +if (NOT USE_SUBMODULE_FMT) + find_package(fmt REQUIRED) +endif () +target_link_libraries(tego_ui PRIVATE fmt::fmt-header-only) + +# QT +target_link_libraries( + tego_ui + PRIVATE Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Widgets + Qt${QT_VERSION_MAJOR}::Network + Qt${QT_VERSION_MAJOR}::Qml + Qt${QT_VERSION_MAJOR}::Quick) +if (APPLE) + target_link_libraries(tego_ui PRIVATE Qt${QT_VERSION_MAJOR}::MacExtras) +endif () + +if ("${CMAKE_BUILD_TYPE}" MATCHES "Rel.*" OR "${CMAKE_BUILD_TYPE}" STREQUAL "MinSizeRel") + target_compile_definitions(tego_ui PRIVATE QT_NO_DEBUG_OUTPUT QT_NO_WARNING_OUTPUT) +endif () diff --git a/src/libtego_ui/libtego_callbacks.cpp b/src/libtego_ui/libtego_callbacks.cpp new file mode 100644 index 00000000..b7fb1a73 --- /dev/null +++ b/src/libtego_ui/libtego_callbacks.cpp @@ -0,0 +1,682 @@ +#include "utils/Settings.h" +#include "utils/Useful.h" +#include "shims/TorControl.h" +#include "shims/TorManager.h" +#include "shims/UserIdentity.h" +#include "shims/ConversationModel.h" +#include "shims/OutgoingContactRequest.h" + +namespace +{ + constexpr int consumeInterval = 10; + + // this holds a callback which can be called and then deletes the underlying data + // replaces std::function beause std::function cannot be move constructed >:[ + class run_once_task + { + public: + run_once_task() = default; + run_once_task(run_once_task&& that) + : run_once_task() + { + *this = std::move(that); + } + + // no copying allowed + run_once_task(const run_once_task&) = delete; + run_once_task& operator=(run_once_task const&) = delete; + + // ensure move does not overwrite existing callback data + run_once_task& operator=(run_once_task&& that) + { + Q_ASSERT(exec == nullptr && callable == nullptr); + exec = that.exec; + callable = that.callable; + + that.exec = nullptr; + that.callable = nullptr; + + return *this; + } + + template + run_once_task(LAMBDA&& lambda) + { + // convertible to raw ptr + if constexpr (std::is_convertible::value) + { + exec = [](run_once_task* self) -> void + { + auto fn = reinterpret_cast(self->callable); + fn(); + + self->exec = nullptr; + self->callable = nullptr; + }; + callable = reinterpret_cast(static_cast(lambda)); + } + // otherwise make a heap copy + else + { + exec = [](run_once_task* self) -> void + { + auto fn = reinterpret_cast(self->callable); + (*fn)(); + delete fn; + + self->exec = nullptr; + self->callable = nullptr; + }; + callable = new LAMBDA(std::move(lambda)); + } + } + + // just ensure we're not messing anything up + ~run_once_task() + { + Q_ASSERT(exec == nullptr && callable == nullptr); + } + + + void operator()() + { + exec(this); + } + + private: + void(*exec)(run_once_task*) = nullptr; + void* callable = nullptr; + }; + + // data + std::vector taskQueue; + std::mutex taskQueueLock; + + void consume_tasks() + { + // get sole access to the task queue + static decltype(taskQueue) localTaskQueue; + { + std::lock_guard lock(taskQueueLock); + std::swap(taskQueue, localTaskQueue); + } + + // consume all of our tasks + for(auto& task : localTaskQueue) + { + try + { + task(); + } + catch(std::exception& ex) + { + qDebug() << "Exception thrown from task: " << ex.what(); + } + } + + // clear out our queue + localTaskQueue.clear(); + + // schedule us to run again + QTimer::singleShot(consumeInterval, &consume_tasks); + } + + template + void push_task(FUNC&& func) + { + // acquire lock on the queue and push our received functor + std::lock_guard lock(taskQueueLock); + taskQueue.push_back(std::move(func)); + } + + QString serviceIdToContactId(const QString& serviceId) + { + return QStringLiteral("ricochet:%1").arg(serviceId); + } + + QString tegoUserIdToServiceId(const tego_user_id_t* user) + { + std::unique_ptr serviceId; + tego_user_id_get_v3_onion_service_id(user, tego::out(serviceId), tego::throw_on_error()); + + char serviceIdRaw[TEGO_V3_ONION_SERVICE_ID_SIZE] = {0}; + tego_v3_onion_service_id_to_string(serviceId.get(), serviceIdRaw, sizeof(serviceIdRaw), tego::throw_on_error()); + + auto contactId = QString::fromUtf8(serviceIdRaw, TEGO_V3_ONION_SERVICE_ID_LENGTH); + return contactId; + } + + // converts the our tego_user_id_t to ricochet's contactId in the form ricochet:serviceidserviceidserviceid... + QString tegoUserIdToContactId(const tego_user_id_t* user) + { + return serviceIdToContactId(tegoUserIdToServiceId(user)); + } + + shims::ContactUser* contactUserFromContactId(const QString& contactId) + { + auto userIdentity = shims::UserIdentity::userIdentity; + auto contactsManager = userIdentity->getContacts(); + + auto contactUser = contactsManager->getShimContactByContactId(contactId); + return contactUser; + } + + // + // libtego callbacks + // + + void on_tor_error_occurred( + tego_context_t*, + tego_tor_error_origin_t origin, + const tego_error_t* error) + { + // route the error message to the appropriate component + QString errorMsg = tego_error_get_message(error); + logger::println("tor error : {}", errorMsg); + push_task([=]() -> void + { + switch(origin) + { + case tego_tor_error_origin_control: + { + shims::TorControl::torControl->setErrorMessage(errorMsg); + } + break; + case tego_tor_error_origin_manager: + { + shims::TorManager::torManager->setErrorMessage(errorMsg); + } + break; + } + }); + } + + void on_update_tor_daemon_config_succeeded( + tego_context_t*, + tego_bool_t success) + { + push_task([=]() -> void + { + logger::println("tor daemon config succeeded : {}", success); + auto torControl = shims::TorControl::torControl; + if (torControl->m_setConfigurationCommand != nullptr) + { + torControl->m_setConfigurationCommand->onFinished(success); + torControl->m_setConfigurationCommand = nullptr; + } + }); + } + + void on_tor_control_status_changed( + tego_context_t*, + tego_tor_control_status_t status) + { + push_task([=]() -> void + { + logger::println("new control status : {}", status); + shims::TorControl::torControl->setStatus(static_cast(status)); + }); + } + + void on_tor_process_status_changed( + tego_context_t*, + tego_tor_process_status_t status) + { + push_task([=]() -> void + { + logger::println("new process status : {}", status); + auto torManager = shims::TorManager::torManager; + switch(status) + { + case tego_tor_process_status_running: + torManager->setRunning("Yes"); + break; + case tego_tor_process_status_external: + torManager->setRunning("External"); + break; + default: + torManager->setRunning("No"); + break; + } + }); + } + + void on_tor_network_status_changed( + tego_context_t*, + tego_tor_network_status_t status) + { + push_task([=]() -> void + { + logger::println("new network status : {}", status); + auto torControl = shims::TorControl::torControl; + switch(status) + { + case tego_tor_network_status_unknown: + torControl->setTorStatus(shims::TorControl::TorUnknown); + break; + case tego_tor_network_status_ready: + torControl->setTorStatus(shims::TorControl::TorReady); + break; + case tego_tor_network_status_offline: + torControl->setTorStatus(shims::TorControl::TorOffline); + break; + } + }); + } + + void on_tor_bootstrap_status_changed( + tego_context_t*, + int32_t progress, + tego_tor_bootstrap_tag_t tag) + { + push_task([=]() -> void + { + logger::println("bootstrap status : {{ progress : {}, tag : {} }}", progress, static_cast(tag)); + auto torControl = shims::TorControl::torControl; + emit torControl->bootstrapStatusChanged(); + }); + } + + void on_tor_log_received( + tego_context_t*, + const char* message, + size_t messageLength) + { + auto messageString = QString::fromUtf8(message, safe_cast(messageLength)); + push_task([=]()-> void + { + auto torManager = shims::TorManager::torManager; + emit torManager->logMessage(messageString); + }); + } + + void on_host_user_state_changed( + tego_context_t*, + tego_host_user_state_t state) + { + logger::println("new host user state : {}", state); + push_task([=]() -> void + { + auto userIdentity = shims::UserIdentity::userIdentity; + switch(state) + { + case tego_host_user_state_offline: + userIdentity->setOnline(false); + break; + case tego_host_user_state_online: + userIdentity->setOnline(true); + break; + default: + break; + } + }); + } + + void on_chat_request_received( + tego_context_t*, + const tego_user_id_t* userId, + const char* message, + size_t messageLength) + { + logger::println("Received chat request from {}", tegoUserIdToServiceId(userId)); + logger::println("Message : {}", message); + + auto hostname = tegoUserIdToServiceId(userId) + ".onion"; + auto messageString = QString::fromUtf8(message, safe_cast(messageLength)); + + push_task([=]() -> void + { + auto userIdentity = shims::UserIdentity::userIdentity; + userIdentity->createIncomingContactRequest(hostname, messageString); + }); + } + + void on_chat_request_response_received( + tego_context_t*, + const tego_user_id_t* userId, + tego_bool_t requestAccepted) + { + logger::trace(); + + auto serviceId = tegoUserIdToServiceId(userId); + + push_task([=]() -> void + { + auto userIdentity = shims::UserIdentity::userIdentity; + auto contactsManager = userIdentity->getContacts(); + auto contact = contactsManager->getShimContactByContactId(serviceIdToContactId(serviceId)); + auto outgoingContactRequest = contact->contactRequest(); + + logger::trace(); + + if (requestAccepted) + { + outgoingContactRequest->setAccepted(); + } + else + { + outgoingContactRequest->setRejected(); + contact->setStatus(shims::ContactUser::RequestRejected); + } + }); + } + + void on_user_status_changed( + tego_context_t*, + const tego_user_id_t* userId, + tego_user_status_t status) + { + logger::trace(); + auto serviceId = tegoUserIdToServiceId(userId); + + logger::println("user status changed -> service id : {}, status : {}", serviceId, static_cast(status)); + + push_task([=]() -> void + { + auto userIdentity = shims::UserIdentity::userIdentity; + auto contactsManager = userIdentity->getContacts(); + auto contact = contactsManager->getShimContactByContactId(serviceIdToContactId(serviceId)); + auto conversation = contact->conversation(); + + if (contact != nullptr) + { + switch(status) + { + case tego_user_status_online: + contact->setStatus(shims::ContactUser::Online); + contactsManager->setContactStatus(contact, shims::ContactUser::Online); + conversation->setStatus(shims::ContactUser::Online); + break; + case tego_user_status_offline: + contact->setStatus(shims::ContactUser::Offline); + contactsManager->setContactStatus(contact, shims::ContactUser::Offline); + conversation->setStatus(shims::ContactUser::Offline); + break; + default: + break; + } + } + }); + } + + void on_message_received( + tego_context_t*, + const tego_user_id_t* sender, + tego_time_t timestamp, + tego_message_id_t messageId, + const char* message, + size_t messageLength) + { + auto contactId = tegoUserIdToContactId(sender); + auto messageString = QString::fromUtf8(message, safe_cast(messageLength)); + + push_task([=]() -> void + { + auto contactUser = contactUserFromContactId(contactId); + Q_ASSERT(contactUser != nullptr); + auto conversationModel = contactUser->conversation(); + Q_ASSERT(conversationModel != nullptr); + + conversationModel->messageReceived(messageId, QDateTime::fromMSecsSinceEpoch(safe_cast(timestamp)), messageString); + }); + } + + void on_message_acknowledged( + tego_context_t*, + const tego_user_id_t* userId, + tego_message_id_t messageId, + tego_bool_t messageAccepted) + { + logger::trace(); + logger::println(" userId : {}", static_cast(userId)); + logger::println(" messageId : {}", messageId); + logger::println(" messageAccepted : {}", messageAccepted); + + QString contactId = tegoUserIdToContactId(userId); + push_task([=]() -> void + { + logger::trace(); + auto contactsManager = shims::UserIdentity::userIdentity->getContacts(); + auto contactUser = contactsManager->getShimContactByContactId(contactId); + auto conversationModel = contactUser->conversation(); + conversationModel->messageAcknowledged(messageId, static_cast(messageAccepted)); + }); + } + + void on_file_transfer_request_received( + tego_context_t*, + tego_user_id_t const* sender, + tego_file_transfer_id_t id, + char const* fileName, + size_t fileNameLength, + tego_file_size_t fileSize, + tego_file_hash_t const* fileHash) + { + auto contactId = tegoUserIdToContactId(sender); + QString fileNameCopy = QString::fromUtf8(fileName, safe_cast(fileNameLength)); + auto hashStr = tego::to_string(fileHash); + + push_task([=,fileName=std::move(fileNameCopy)]() -> void + { + auto contactUser = contactUserFromContactId(contactId); + Q_ASSERT(contactUser != nullptr); + auto conversationModel = contactUser->conversation(); + Q_ASSERT(conversationModel != nullptr); + + conversationModel->fileTransferRequestReceived(id, fileName, QString::fromStdString(hashStr), fileSize); + }); + } + + void on_file_transfer_request_acknowledged( + tego_context_t*, + tego_user_id_t const* receiver, + tego_file_transfer_id_t id, + tego_bool_t ack) + { + auto contactId = tegoUserIdToContactId(receiver); + + push_task([=]() -> void + { + auto contactUser = contactUserFromContactId(contactId); + Q_ASSERT(contactUser != nullptr); + auto conversationModel = contactUser->conversation(); + Q_ASSERT(conversationModel != nullptr); + + conversationModel->fileTransferRequestAcknowledged(id, ack); + }); + } + + void on_file_transfer_request_response_received( + tego_context_t*, + tego_user_id_t const* receiver, + tego_file_transfer_id_t id, + tego_file_transfer_response_t response) + { + auto contactId = tegoUserIdToContactId(receiver); + + push_task([=]() -> void + { + auto contactUser = contactUserFromContactId(contactId); + Q_ASSERT(contactUser != nullptr); + auto conversationModel = contactUser->conversation(); + Q_ASSERT(conversationModel != nullptr); + + conversationModel->fileTransferRequestResponded(id, response); + }); + } + + void on_file_transfer_progress( + tego_context_t*, + const tego_user_id_t* userId, + tego_file_transfer_id_t id, + tego_file_transfer_direction_t direction, + tego_file_size_t bytesComplete, + tego_file_size_t bytesTotal) + { + auto contactId = tegoUserIdToContactId(userId); + + push_task([=]() -> void + { + auto contactUser = contactUserFromContactId(contactId); + Q_ASSERT(contactUser != nullptr); + auto conversationModel = contactUser->conversation(); + Q_ASSERT(conversationModel != nullptr); + + conversationModel->fileTransferRequestProgressUpdated(id, bytesComplete); + }); + + + logger::println( + "File Progress id : {}, direction : {}, transferred : {} bytes, total : {} bytes", + id, + direction == tego_file_transfer_direction_sending ? "sending" : "receiving", + bytesComplete, + bytesTotal); + } + + void on_file_transfer_complete( + tego_context_t*, + const tego_user_id_t* userId, + tego_file_transfer_id_t id, + tego_file_transfer_direction_t, + tego_file_transfer_result_t result) + { + auto contactId = tegoUserIdToContactId(userId); + + push_task([=]() -> void + { + auto contactUser = contactUserFromContactId(contactId); + Q_ASSERT(contactUser != nullptr); + auto conversationModel = contactUser->conversation(); + Q_ASSERT(conversationModel != nullptr); + + conversationModel->fileTransferRequestCompleted(id, result); + }); + } + + void on_new_identity_created( + tego_context_t*, + const tego_ed25519_private_key_t* privateKey) + { + // convert privateKey to KeyBlob + char rawKeyBlob[TEGO_ED25519_KEYBLOB_SIZE] = {0}; + tego_ed25519_keyblob_from_ed25519_private_key( + rawKeyBlob, + sizeof(rawKeyBlob), + privateKey, + tego::throw_on_error()); + + QString keyBlob(rawKeyBlob); + + push_task([=]() -> void + { + SettingsObject so(QStringLiteral("identity")); + so.write("privateKey", keyBlob); + }); + } +} + +void init_libtego_callbacks(tego_context_t* context) +{ + // start triggering our consume queue + QTimer::singleShot(consumeInterval, &consume_tasks); + + // + // register each of our callbacks with libtego + // + + tego_context_set_tor_error_occurred_callback( + context, + &on_tor_error_occurred, + tego::throw_on_error()); + + tego_context_set_update_tor_daemon_config_succeeded_callback( + context, + &on_update_tor_daemon_config_succeeded, + tego::throw_on_error()); + + tego_context_set_tor_control_status_changed_callback( + context, + &on_tor_control_status_changed, + tego::throw_on_error()); + + tego_context_set_tor_process_status_changed_callback( + context, + &on_tor_process_status_changed, + tego::throw_on_error()); + + tego_context_set_tor_network_status_changed_callback( + context, + &on_tor_network_status_changed, + tego::throw_on_error()); + + tego_context_set_tor_bootstrap_status_changed_callback( + context, + &on_tor_bootstrap_status_changed, + tego::throw_on_error()); + + tego_context_set_tor_log_received_callback( + context, + &on_tor_log_received, + tego::throw_on_error()); + + tego_context_set_host_user_state_changed_callback( + context, + &on_host_user_state_changed, + tego::throw_on_error()); + + tego_context_set_chat_request_received_callback( + context, + &on_chat_request_received, + tego::throw_on_error()); + + tego_context_set_chat_request_response_received_callback( + context, + &on_chat_request_response_received, + tego::throw_on_error()); + + tego_context_set_file_transfer_request_received_callback( + context, + &on_file_transfer_request_received, + tego::throw_on_error()); + + tego_context_set_file_transfer_request_acknowledged_callback( + context, + &on_file_transfer_request_acknowledged, + tego::throw_on_error()); + + tego_context_set_file_transfer_request_response_received_callback( + context, + &on_file_transfer_request_response_received, + tego::throw_on_error()); + + tego_context_set_file_transfer_progress_callback( + context, + &on_file_transfer_progress, + tego::throw_on_error()); + + tego_context_set_file_transfer_complete_callback( + context, + &on_file_transfer_complete, + tego::throw_on_error()); + + tego_context_set_user_status_changed_callback( + context, + &on_user_status_changed, + tego::throw_on_error()); + + tego_context_set_message_received_callback( + context, + &on_message_received, + tego::throw_on_error()); + + tego_context_set_message_acknowledged_callback( + context, + &on_message_acknowledged, + tego::throw_on_error()); + + tego_context_set_new_identity_created_callback( + context, + &on_new_identity_created, + tego::throw_on_error()); +} diff --git a/src/libtego_ui/libtego_callbacks.hpp b/src/libtego_ui/libtego_callbacks.hpp new file mode 100644 index 00000000..eac1fa17 --- /dev/null +++ b/src/libtego_ui/libtego_callbacks.hpp @@ -0,0 +1,3 @@ +#pragma once + +void init_libtego_callbacks(tego_context_t* context); \ No newline at end of file diff --git a/src/libtego_ui/precomp.hpp b/src/libtego_ui/precomp.hpp new file mode 100644 index 00000000..5f9753da --- /dev/null +++ b/src/libtego_ui/precomp.hpp @@ -0,0 +1,45 @@ +#include + +// C headers + +// standard library +#include + +// C++ headers +#ifdef __cplusplus + +// standard library +#include +#include +#include +#include +#include +#include +#include +#include + +// fmt +#include +#include + +// Qt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef Q_OS_MAC +# include +#endif // Q_OS_MAC + +// tego +#include + +#endif // __cplusplus + + diff --git a/src/libtego_ui/shims/ContactIDValidator.cpp b/src/libtego_ui/shims/ContactIDValidator.cpp new file mode 100644 index 00000000..f6813f0f --- /dev/null +++ b/src/libtego_ui/shims/ContactIDValidator.cpp @@ -0,0 +1,88 @@ +#include "UserIdentity.h" +#include "ContactIDValidator.h" + +namespace shims +{ + ContactIDValidator::ContactIDValidator(QObject *parent) + : QRegularExpressionValidator(parent) + { + QRegularExpressionValidator::setRegularExpression(QRegularExpression(QStringLiteral("ricochet:([a-z2-7]{56})"))); + } + + void ContactIDValidator::fixup(QString &text) const + { + logger::trace(); + text = text.trimmed().toLower(); + } + + QValidator::State ContactIDValidator::validate(QString &text, int &pos) const + { + logger::trace(); + fixup(text); + + QValidator::State re = QRegularExpressionValidator::validate(text, pos); + if (re != QValidator::Acceptable) + { + if (re == QValidator::Invalid) + { + emit failed(); + } + else + { + emit success(); // removes the popup when the id returns to being valid + } + return re; + } + + if (!isValidID(text) || matchingContact(text) || matchesIdentity(text)) + { + emit failed(); + return QValidator::Invalid; + } + + emit success(); + return re; + } + + shims::ContactUser* ContactIDValidator::matchingContact(const QString &text) const + { + logger::trace(); + logger::println("UserIdentity instance : {}", static_cast(UserIdentity::userIdentity)); + auto contactsManager = UserIdentity::userIdentity->getContacts(); + logger::println("ContactsManager : {}", static_cast(contactsManager)); + return contactsManager->getShimContactByContactId(text); + } + + bool ContactIDValidator::matchesIdentity(const QString &text) const + { + logger::trace(); + logger::println("UserIdentity instance : {}", static_cast(UserIdentity::userIdentity)); + auto context = UserIdentity::userIdentity->getContext(); + logger::println("context : {}", static_cast(UserIdentity::userIdentity->getContext())); + + std::unique_ptr userId; + tego_context_get_host_user_id(context, tego::out(userId), tego::throw_on_error()); + + std::unique_ptr serviceId; + tego_user_id_get_v3_onion_service_id(userId.get(), tego::out(serviceId), tego::throw_on_error()); + + char serviceIdString[TEGO_V3_ONION_SERVICE_ID_SIZE] = {0}; + tego_v3_onion_service_id_to_string(serviceId.get(), serviceIdString, sizeof(serviceIdString), tego::throw_on_error()); + + auto utf8Text = text.mid(tego::static_strlen("ricochet:")).toUtf8(); + auto utf8ServiceId = QByteArray(serviceIdString, TEGO_V3_ONION_SERVICE_ID_LENGTH); + + return utf8Text == utf8ServiceId; + } + + bool ContactIDValidator::isValidID(const QString &serviceID) const + { + auto strippedID = serviceID.mid(tego::static_strlen("ricochet:")); + logger::println("strippedID : {}", strippedID.toUtf8().constData(), strippedID.size()); + + bool valid = tego_v3_onion_service_id_string_is_valid(strippedID.toUtf8().constData(), static_cast(strippedID.size()), nullptr) == TEGO_TRUE; + logger::println("valid: {}", valid); + + return valid; + } +} diff --git a/src/libtego_ui/shims/ContactIDValidator.h b/src/libtego_ui/shims/ContactIDValidator.h new file mode 100644 index 00000000..74171204 --- /dev/null +++ b/src/libtego_ui/shims/ContactIDValidator.h @@ -0,0 +1,23 @@ +#pragma once + +namespace shims +{ + class ContactUser; + class ContactIDValidator : public QRegularExpressionValidator + { + Q_OBJECT + Q_DISABLE_COPY(ContactIDValidator) + public: + ContactIDValidator(QObject *parent = 0); + + virtual void fixup(QString &text) const; + virtual State validate(QString &text, int &pos) const; + + Q_INVOKABLE bool isValidID(const QString &serviceID) const; + Q_INVOKABLE shims::ContactUser *matchingContact(const QString &text) const; + Q_INVOKABLE bool matchesIdentity(const QString &text) const; + signals: + void failed() const; + void success() const; + }; +} \ No newline at end of file diff --git a/src/libtego_ui/shims/ContactUser.cpp b/src/libtego_ui/shims/ContactUser.cpp new file mode 100644 index 00000000..65eed623 --- /dev/null +++ b/src/libtego_ui/shims/ContactUser.cpp @@ -0,0 +1,127 @@ +#include "UserIdentity.h" +#include "ContactUser.h" +#include "ConversationModel.h" +#include "OutgoingContactRequest.h" + +// TODO: wire up the slots in here, figure out how to properly wire up unread count, status change +// populating the contacts manager on boot, keeping libtego's internal contacts synced with frontend's +// put all of the SettingsObject stuff into one settings manager + +namespace shims +{ + ContactUser::ContactUser(const QString& serviceId_, const QString& nickname_) + : conversationModel(new shims::ConversationModel(this)) + , outgoingContactRequest(new shims::OutgoingContactRequest()) + , status(ContactUser::Offline) + , serviceId(serviceId_) + , nickname() + , settings(QString("users.%1").arg(serviceId_)) + { + Q_ASSERT(serviceId.size() == TEGO_V3_ONION_SERVICE_ID_LENGTH); + conversationModel->setContact(this); + + + this->setNickname(nickname_); + } + + QString ContactUser::getNickname() const + { + return nickname; + } + + QString ContactUser::getContactID() const + { + return QString("ricochet:") + serviceId; + } + + ContactUser::Status ContactUser::getStatus() const + { + return status; + } + + void ContactUser::setStatus(ContactUser::Status newStatus) + { + if (this->status != newStatus) + { + this->status = newStatus; + switch(this->status) + { + case ContactUser::Online: + case ContactUser::Offline: + settings.write("type", "allowed"); + break; + case ContactUser::RequestPending: + settings.write("type", "pending"); + break; + case ContactUser::RequestRejected: + settings.write("type", "rejected"); + break; + default: + break; + } + emit this->statusChanged(); + } + } + + shims::OutgoingContactRequest* ContactUser::contactRequest() + { + return outgoingContactRequest; + } + + shims::ConversationModel* ContactUser::conversation() + { + return conversationModel; + } + + void ContactUser::setNickname(const QString& newNickname) + { + if (this->nickname != newNickname) + { + this->nickname = newNickname; + settings.write("nickname", newNickname); + emit this->nicknameChanged(); + } + } + + void ContactUser::deleteContact() + { + auto userIdentity = shims::UserIdentity::userIdentity; + + auto context = userIdentity->getContext(); + auto userId = this->toTegoUserId(); + + tego_context_forget_user(context, userId.get(), tego::throw_on_error()); + + settings.undefine(); + emit this->contactDeleted(this); + } + + void ContactUser::sendFile() + { + this->conversationModel->sendFile(); + } + + bool ContactUser::exportConversation() + { + return this->conversationModel->exportConversation(); + } + + std::unique_ptr ContactUser::toTegoUserId() const + { + logger::println("serviceId : {}", this->serviceId); + + auto serviceIdRaw = this->serviceId.toUtf8(); + + // ensure valid service id + std::unique_ptr onionServiceId; + tego_v3_onion_service_id_from_string(tego::out(onionServiceId), serviceIdRaw.data(), static_cast(serviceIdRaw.size()), tego::throw_on_error()); + + logger::trace(); + + // create user id object from service id + std::unique_ptr userId; + tego_user_id_from_v3_onion_service_id(tego::out(userId), onionServiceId.get(), tego::throw_on_error()); + + return userId; + } +} diff --git a/src/libtego_ui/shims/ContactUser.h b/src/libtego_ui/shims/ContactUser.h new file mode 100644 index 00000000..2ecd86b3 --- /dev/null +++ b/src/libtego_ui/shims/ContactUser.h @@ -0,0 +1,67 @@ +#pragma once + +#include "utils/Settings.h" + +namespace shims +{ + class ContactsManager; + class ConversationModel; + class OutgoingContactRequest; + class ContactUser : public QObject + { + Q_OBJECT + Q_DISABLE_COPY(ContactUser) + Q_ENUMS(Status) + + Q_PROPERTY(QString nickname READ getNickname WRITE setNickname NOTIFY nicknameChanged) + Q_PROPERTY(QString contactID READ getContactID CONSTANT) + Q_PROPERTY(Status status READ getStatus NOTIFY statusChanged) + Q_PROPERTY(shims::OutgoingContactRequest* contactRequest READ contactRequest NOTIFY statusChanged) + Q_PROPERTY(shims::ConversationModel* conversation READ conversation CONSTANT) + public: + enum Status + { + Online, + Offline, + RequestPending, + RequestRejected, + Outdated + }; + + ContactUser(const QString& serviceId, const QString& nickname); + + QString getNickname() const; + QString getContactID() const; + Status getStatus() const; + void setStatus(Status status); + shims::OutgoingContactRequest *contactRequest(); + shims::ConversationModel *conversation(); + + Q_INVOKABLE void deleteContact(); + Q_INVOKABLE void sendFile(); + Q_INVOKABLE bool exportConversation(); + + std::unique_ptr toTegoUserId() const; + + public slots: + void setNickname(const QString &nickname); + + signals: + void nicknameChanged(); + void statusChanged(); + void contactDeleted(shims::ContactUser *user); + + private: + shims::ConversationModel* conversationModel; + shims::OutgoingContactRequest* outgoingContactRequest; + + Status status; + QString serviceId; + QString nickname; + + SettingsObject settings; + + friend class shims::ContactsManager; + friend class shims::ConversationModel; + }; +} \ No newline at end of file diff --git a/src/libtego_ui/shims/ContactsManager.cpp b/src/libtego_ui/shims/ContactsManager.cpp new file mode 100644 index 00000000..eec6304d --- /dev/null +++ b/src/libtego_ui/shims/ContactsManager.cpp @@ -0,0 +1,89 @@ +#include "ContactsManager.h" +#include "ContactUser.h" + +namespace shims +{ + ContactsManager::ContactsManager(tego_context_t* context_) + : context(context_) + , contactsList({}) + { } + + shims::ContactUser* ContactsManager::createContactRequest( + const QString &contactID, + const QString &nickname, + const QString &myNickname, + const QString &message) + { + logger::println("{{ contactID : {}, nickname : {}, myNickname : {}, message : {} }}", + contactID, nickname, myNickname, message); + + auto serviceId = contactID.mid(tego::static_strlen("ricochet:")).toUtf8(); + + // check that the service id is valid before anything else + if (tego_v3_onion_service_id_string_is_valid(serviceId.constData(), static_cast(serviceId.size()), nullptr) != TEGO_TRUE) + { + return nullptr; + } + + auto shimContact = this->addContact(serviceId, nickname); + + auto userId = shimContact->toTegoUserId(); + auto rawMessage = message.toUtf8(); + + tego_context_send_chat_request(this->context, userId.get(), rawMessage.data(), static_cast(rawMessage.size()), tego::throw_on_error()); + + shimContact->setStatus(shims::ContactUser::RequestPending); + + return shimContact; + } + + shims::ContactUser* ContactsManager::addContact(const QString& serviceId, const QString& nickname) + { + // creates a new contact from service id and nickname + auto shimContact = new shims::ContactUser(serviceId, nickname); + contactsList.push_back(shimContact); + + // remove our reference and ready for deleting when contactDeleted signal is fireds + connect(shimContact, &shims::ContactUser::contactDeleted, [self=this](shims::ContactUser* user) -> void + { + // find the given user in our internal list and remove, mark for deletion + auto it = std::find(self->contactsList.begin(), self->contactsList.end(), user); + self->contactsList.erase(it); + + user->deleteLater(); + }); + + emit this->contactAdded(shimContact); + return shimContact; + } + + shims::ContactUser* ContactsManager::getShimContactByContactId(const QString& contactId) const + { + logger::trace(); + for(auto& cu : contactsList) + { + logger::println("cu : {}", static_cast(cu)); + if (cu->getContactID() == contactId) + { + logger::trace(); + return cu; + } + } + return nullptr; + } + + const QList& ContactsManager::contacts() const + { + return contactsList; + } + + void ContactsManager::setUnreadCount(shims::ContactUser* user, int unreadCount) + { + emit this->unreadCountChanged(user, unreadCount); + } + + void ContactsManager::setContactStatus(shims::ContactUser* user, int status) + { + emit this->contactStatusChanged(user, status); + } +} diff --git a/src/libtego_ui/shims/ContactsManager.h b/src/libtego_ui/shims/ContactsManager.h new file mode 100644 index 00000000..d320edcf --- /dev/null +++ b/src/libtego_ui/shims/ContactsManager.h @@ -0,0 +1,34 @@ +#pragma once + +#include "ContactUser.h" + +namespace shims +{ + class ContactsManager : public QObject + { + Q_OBJECT + Q_DISABLE_COPY(ContactsManager) + public: + ContactsManager(tego_context_t* context); + + Q_INVOKABLE shims::ContactUser* createContactRequest( + const QString &contactID, + const QString &nickname, + const QString &myNickname, + const QString &message); + shims::ContactUser* addContact(const QString& serviceId, const QString& nickname); + const QList& contacts() const; + shims::ContactUser* getShimContactByContactId(const QString& contactId) const; + + void setUnreadCount(shims::ContactUser* user, int unreadCount); + void setContactStatus(shims::ContactUser* user, int status); + + signals: + void contactAdded(shims::ContactUser *user); + void unreadCountChanged(shims::ContactUser *user, int unreadCount); + void contactStatusChanged(shims::ContactUser* user, int status); + private: + tego_context_t* context; + mutable QList contactsList; + }; +} diff --git a/src/libtego_ui/shims/ConversationModel.cpp b/src/libtego_ui/shims/ConversationModel.cpp new file mode 100644 index 00000000..72daa891 --- /dev/null +++ b/src/libtego_ui/shims/ConversationModel.cpp @@ -0,0 +1,806 @@ +#include "ContactUser.h" +#include "ConversationModel.h" +#include "UserIdentity.h" +#include "utils/Useful.h" + +namespace shims +{ + ConversationModel::ConversationModel(QObject *parent) + : QAbstractListModel(parent) + , contactUser(nullptr) + , messages({}) + , unreadCount(0) + { + connect(this, &ConversationModel::unreadCountChanged, [self=this](int prevCount, int currentCount) -> void + { + static int globalUnreadCount = 0; + + const auto delta = currentCount - prevCount; + globalUnreadCount += delta; + + qDebug() << "globalUnreadCount:" << globalUnreadCount; +#ifdef Q_OS_MAC + QtMac::setBadgeLabelText(globalUnreadCount == 0 ? QString() : QString::number(globalUnreadCount)); +#endif + }); + } + + QHash ConversationModel::roleNames() const + { + QHash roles; + roles[Qt::DisplayRole] = "text"; + roles[TimestampRole] = "timestamp"; + roles[IsOutgoingRole] = "isOutgoing"; + roles[StatusRole] = "status"; + roles[SectionRole] = "section"; + roles[TimespanRole] = "timespan"; + roles[TypeRole] = "type"; + roles[TransferRole] = "transfer"; + return roles; + } + + int ConversationModel::rowCount(const QModelIndex &parent) const + { + if (parent.isValid()) + return 0; + return messages.size(); + } + + QVariant ConversationModel::data(const QModelIndex &index, int role) const + { + if (!index.isValid() || index.row() >= messages.size()) + return QVariant(); + + const MessageData &message = messages[index.row()]; + + switch (role) { + case Qt::DisplayRole: + if (message.type == TextMessage) + { + return message.text; + } + else + { + return QStringLiteral("not a text message"); + } + + case TimestampRole: return message.time; + case IsOutgoingRole: return message.status != Received; + case StatusRole: return message.status; + + case SectionRole: { + if (contact()->getStatus() == ContactUser::Online) + return QString(); + if (index.row() < messages.size() - 1) { + const MessageData &next = messages[index.row()+1]; + if (next.status != Received && next.status != Delivered) + return QString(); + } + for (int i = 0; i <= index.row(); i++) { + if (messages[i].status == Received || messages[i].status == Delivered) + return QString(); + } + return QStringLiteral("offline"); + } + case TimespanRole: { + if (index.row() < messages.size() - 1) + return messages[index.row() + 1].time.secsTo(messages[index.row()].time); + else + return -1; + } + case TypeRole: { + if (message.type == TextMessage) { + return QStringLiteral("text"); + } + else if (message.type == TransferMessage) { + return QStringLiteral("transfer"); + } + else { + return QStringLiteral("invalid"); + } + case TransferRole: + if (message.type == TransferMessage) + { + QVariantMap transfer; + transfer["file_name"] = message.fileName; + transfer["file_size"] = message.fileSize; + transfer["file_hash"] = message.fileHash; + transfer["id"] = message.identifier; + transfer["status"] = message.transferStatus; + transfer["statusString"] = [=]() + { + switch(message.transferStatus) + { + case Pending: return tr("Pending"); + case Accepted: return tr("Accepted"); + case Rejected: return tr("Rejected"); + case InProgress: + { + const auto locale = QLocale::system(); + return QString("%1 / %2").arg(locale.formattedDataSize(safe_cast(message.bytesTransferred)), locale.formattedDataSize(message.fileSize)); + } + case Cancelled: return tr("Cancelled"); + case Finished: return tr("Complete"); + case UnknownFailure: return tr("Unkown Failure"); + case BadFileHash: return tr("Bad File Hash"); + case NetworkError: return tr("Network Error"); + case FileSystemError: return tr("File System Error"); + + default: return tr("Invalid"); + } + }(); + transfer["progressPercent"] = double(message.bytesTransferred) / double(message.fileSize); + transfer["direction"] = message.transferDirection; + + return transfer; + } + } + } + + return QVariant(); + } + + shims::ContactUser* ConversationModel::contact() const + { + return contactUser; + } + + void ConversationModel::setContact(shims::ContactUser *contact) + { + this->contactUser = contact; + emit contactChanged(); + } + + int ConversationModel::getUnreadCount() const + { + return unreadCount; + } + + void ConversationModel::resetUnreadCount() + { + this->setUnreadCount(0); + } + + void ConversationModel::setUnreadCount(int count) + { + Q_ASSERT(count >= 0); + + const auto oldUnreadCount = this->unreadCount; + if(oldUnreadCount != count) + { + this->unreadCount = count; + emit unreadCountChanged(oldUnreadCount, unreadCount); + + auto userIdentity = shims::UserIdentity::userIdentity; + auto contactsManager = userIdentity->getContacts(); + contactsManager->setUnreadCount(this->contactUser, count); + } + } + + void ConversationModel::sendMessage(const QString &text) + { + logger::println("sendMessage : {}", text); + auto userIdentity = shims::UserIdentity::userIdentity; + auto context = userIdentity->getContext(); + + auto utf8Str = text.toUtf8(); + if (utf8Str.size() == 0) + { + return; + } + + const auto userId = this->contactUser->toTegoUserId(); + tego_message_id_t messageId = 0; + + // send message and save off the id associated with it + tego_context_send_message( + context, + userId.get(), + utf8Str.data(), + static_cast(utf8Str.size()), + &messageId, + tego::throw_on_error()); + + // store data locally for UI + MessageData md; + md.type = TextMessage; + md.text = text; + md.time = QDateTime::currentDateTime(); + md.identifier = messageId; + md.status = Queued; + + this->beginInsertRows(QModelIndex(), 0, 0); + this->messages.prepend(std::move(md)); + this->endInsertRows(); + this->addEventFromMessage(indexOfOutgoingMessage(messageId)); + } + + void ConversationModel::sendFile() + { + auto filePath = + QFileDialog::getOpenFileName( + nullptr, + tr("Open File"), + QDir::homePath(), + nullptr); + + if (!filePath.isEmpty()) + { + auto userIdentity = shims::UserIdentity::userIdentity; + auto context = userIdentity->getContext(); + const auto path = filePath.toUtf8(); + const auto userId = this->contactUser->toTegoUserId(); + tego_file_transfer_id_t id; + std::unique_ptr fileHash; + tego_file_size_t fileSize = 0; + + try + { + tego_context_send_file_transfer_request( + context, + userId.get(), + path.data(), + static_cast(path.size()), + &id, + tego::out(fileHash), + &fileSize, + tego::throw_on_error()); + + logger::println("send file request id : {}, hash : {}", id, tego::to_string(fileHash.get())); + + MessageData md; + md.type = TransferMessage; + md.identifier = id; + md.time = QDateTime::currentDateTime(); + md.status = Queued; + + md.fileName = QFileInfo(filePath).fileName(); + md.fileSize = safe_cast(fileSize); + md.fileHash = QString::fromStdString(tego::to_string(fileHash.get())); + md.transferStatus = Pending; + md.transferDirection = Uploading; + + this->beginInsertRows(QModelIndex(), 0, 0); + this->messages.prepend(std::move(md)); + this->endInsertRows(); + + this->addEventFromMessage(indexOfOutgoingMessage(id)); + } + catch(const std::runtime_error& err) + { + qWarning() << err.what(); + } + } + } + + void ConversationModel::deserializeTextMessageEventToFile(const EventData &event, std::ofstream &ofile) const + { + auto &md = this->messages[this->messages.size() - safe_cast(event.messageData.reverseIndex)]; + switch (md.status) + { + case Received: + fmt::print(ofile, "[{}] <{}>: {}\n", + md.time.toString().toStdString(), + this->contact()->getNickname().toStdString(), + md.text.toStdString()); break; + case Delivered: + fmt::print(ofile, "[{}] <{}>: {}\n", + md.time.toString().toStdString(), + tr("me").toStdString(), + md.text.toStdString()); break; + default: + // messages we sent that weren't delivered + fmt::print(ofile, "[{}] <{}> ({}): {}\n", + md.time.toString().toStdString(), + tr("me").toStdString(), + getMessageStatusString(md.status), + md.text.toStdString()); break; + } + } + + void ConversationModel::deserializeTransferMessageEventToFile(const EventData &event, std::ofstream &ofile) const + { + auto &md = this->messages[this->messages.size() - safe_cast(event.transferData.reverseIndex)]; + + if (md.transferDirection == InvalidDirection) + return; + + std::string sender = md.transferDirection == Uploading + ? tr("me").toStdString() + : this->contact()->getNickname().toStdString(); + + switch (event.transferData.status) + { + case Pending: //FALLTHROUGH + case Accepted: //FALLTHROUGH + case Rejected: //FALLTHROUGH + case Cancelled: //FALLTHROUGH + case Finished: + fmt::print(ofile, "[{}] file '{}' from <{}> (hash: {}, size: {:L} bytes): {}\n", + event.time.toString().toStdString(), + md.fileName.toStdString(), + sender, + md.fileHash.toStdString(), + md.fileSize, + getTransferStatusString(event.transferData.status)); break; + case UnknownFailure: //FALLTHROUGH + case BadFileHash: //FALLTHROUGH + case NetworkError: //FALLTHROUGH + case FileSystemError: + fmt::print(ofile, "[{}] file '{}' from <{}> (hash: {}, size: {:L} bytes): Error: {}, bytes transferred: {:L} bytes\n", + event.time.toString().toStdString(), + md.fileName.toStdString(), + sender, + md.fileHash.toStdString(), + md.fileSize, + getTransferStatusString(event.transferData.status), + event.transferData.bytesTransferred); break; + default: + qWarning() << "Invalid transfer status in events"; + break; + } + } + + void ConversationModel::deserializeUserStatusUpdateEventToFile(const EventData &event, std::ofstream &ofile) const + { + if (event.userStatusData.target == UserTargetNone) + return; + + std::string sender = event.userStatusData.target == UserTargetClient + ? tr("me").toStdString() + : this->contact()->getNickname().toStdString(); + + switch (event.userStatusData.status) + { + case ContactUser::Status::Online: + fmt::print(ofile, "[{}] <{}> is now online\n", + event.time.toString().toStdString(), + this->contact()->getNickname().toStdString()); break; + case ContactUser::Status::Offline: + fmt::print(ofile, "[{}] <{}> is now offline\n", + event.time.toString().toStdString(), + this->contact()->getNickname().toStdString()); break; + case ContactUser::Status::RequestPending: + fmt::print(ofile, "[{}] New contact request to <{}>\n", + event.time.toString().toStdString(), + this->contact()->getNickname().toStdString()); break; + case ContactUser::Status::RequestRejected: + fmt::print(ofile, "[{}] Outgoing request to <{}> was rejected\n", + event.time.toString().toStdString(), + this->contact()->getNickname().toStdString()); break; + default: + break; + } + } + + void ConversationModel::deserializeEventToFile(const EventData &event, std::ofstream &ofile) const + { + switch (event.type) + { + case TextMessageEvent: + deserializeTextMessageEventToFile(event, ofile); break; + case TransferMessageEvent: + deserializeTransferMessageEventToFile(event, ofile); break; + case UserStatusUpdateEvent: + deserializeUserStatusUpdateEventToFile(event, ofile); break; + default: + qWarning() << "Unknown event type in events list"; + break; + } + } + + bool ConversationModel::hasEventsToExport() { + return events.size() > 0; + } + + bool ConversationModel::exportConversation() + { + const auto proposedDest = QString("%1/%2-%3.log").arg(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)).arg(this->contact()->getNickname()).arg(this->events.constFirst().time.toString(Qt::ISODate)); + + auto filePath = QFileDialog::getSaveFileName(nullptr, + tr("Save File"), + proposedDest, + "Log files (*.log);;All files (*)"); + + if (filePath.isEmpty()) + return true; + + std::ofstream ofile(filePath.toStdString(), std::ios::out); + if (!ofile.is_open()) + { + qWarning() << "Could not open file " << filePath; + return false; + } + + fmt::print(ofile, "Conversation with {} ({})\n", + this->contact()->getNickname().toStdString(), + this->contact()->getContactID().toStdString()); + + foreach(auto &event, this->events) + { + deserializeEventToFile(event, ofile); + } + + return true; + } + + void ConversationModel::tryAcceptFileTransfer(quint32 id) + { + auto row = this->indexOfIncomingMessage(id); + if (row < 0) + { + return; + } + + auto& data = messages[row]; + + auto proposedDest = QString("%1/%2").arg(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)).arg(data.fileName); + + auto dest = QFileDialog::getSaveFileName( + nullptr, + tr("Save File"), + proposedDest); + + if (!dest.isEmpty()) + { + auto userIdentity = shims::UserIdentity::userIdentity; + auto context = userIdentity->getContext(); + const auto sender = this->contactUser->toTegoUserId(); + const auto destination = dest.toUtf8(); + + try + { + tego_context_respond_file_transfer_request( + context, + sender.get(), + id, + tego_file_transfer_response_accept, + destination.data(), + static_cast(destination.size()), + tego::throw_on_error()); + } + catch(const std::runtime_error& err) + { + qWarning() << err.what(); + } + + data.transferStatus = Accepted; + emitDataChanged(row); + this->addEventFromMessage(row); + } + } + + void ConversationModel::cancelFileTransfer(tego_file_transfer_id_t id) + { + // we get the cancelled callback if we cancel or if the other user cancelled, + // so ensure we only do work if it was the other preson cancelling + auto row = this->indexOfMessage(id); + if (row < 0) + { + return; + } + + MessageData &data = messages[row]; + if (data.transferStatus != Cancelled) + { + data.transferStatus = Cancelled; + emitDataChanged(row); + this->addEventFromMessage(row); + + auto userIdentity = shims::UserIdentity::userIdentity; + auto context = userIdentity->getContext(); + const auto userId = this->contactUser->toTegoUserId(); + + try + { + tego_context_cancel_file_transfer( + context, + userId.get(), + id, + tego::throw_on_error()); + } + catch(const std::runtime_error& err) + { + qWarning() << err.what(); + } + } + } + + void ConversationModel::rejectFileTransfer(quint32 id) + { + auto row = this->indexOfIncomingMessage(id); + if (row < 0) + { + return; + } + + auto& data = messages[row]; + + auto userIdentity = shims::UserIdentity::userIdentity; + auto context = userIdentity->getContext(); + const auto sender = this->contactUser->toTegoUserId(); + + try + { + tego_context_respond_file_transfer_request( + context, + sender.get(), + id, + tego_file_transfer_response_reject, + nullptr, + 0, + tego::throw_on_error()); + } + catch(const std::runtime_error& err) + { + qWarning() << err.what(); + } + + data.transferStatus = Rejected; + emitDataChanged(row); + this->addEventFromMessage(row); + } + + void ConversationModel::fileTransferRequestReceived(tego_file_transfer_id_t id, QString fileName, QString fileHash, quint64 fileSize) + { + MessageData md; + md.type = TransferMessage; + md.identifier = id; + md.time = QDateTime::currentDateTime(); + md.status = Received; + + md.fileName = std::move(fileName); + md.fileHash = std::move(fileHash); + md.fileSize = safe_cast(fileSize); + md.transferDirection = Downloading; + md.transferStatus = Pending; + + this->beginInsertRows(QModelIndex(), 0, 0); + this->messages.prepend(std::move(md)); + this->endInsertRows(); + + this->setUnreadCount(this->unreadCount + 1); + this->addEventFromMessage(indexOfIncomingMessage(id)); + } + + void ConversationModel::fileTransferRequestAcknowledged(tego_file_transfer_id_t id, bool accepted) + { + auto row = this->indexOfOutgoingMessage(id); + Q_ASSERT(row >= 0); + + MessageData &data = messages[row]; + data.status = accepted ? Delivered : Error; + emitDataChanged(row); + } + + void ConversationModel::fileTransferRequestResponded(tego_file_transfer_id_t id, tego_file_transfer_response_t response) + { + auto row = this->indexOfOutgoingMessage(id); + Q_ASSERT(row >= 0); + + MessageData &data = messages[row]; + switch(response) + { + case tego_file_transfer_response_accept: + data.transferStatus = Accepted; + break; + case tego_file_transfer_response_reject: + data.transferStatus = Rejected; + break; + default: + return; + } + + emitDataChanged(row); + this->addEventFromMessage(row); + } + + void ConversationModel::fileTransferRequestProgressUpdated(tego_file_transfer_id_t id, quint64 bytesTransferred) + { + auto row = this->indexOfMessage(id); + if (row >= 0) + { + MessageData &data = messages[row]; + data.bytesTransferred = bytesTransferred; + data.transferStatus = InProgress; + + emitDataChanged(row); + } + } + + void ConversationModel::fileTransferRequestCompleted( + tego_file_transfer_id_t id, tego_file_transfer_result_t result) + { + auto row = this->indexOfMessage(id); + if (row >= 0) + { + auto &data = messages[row]; + switch(result) + { + case tego_file_transfer_result_success: + data.transferStatus = Finished; + break; + case tego_file_transfer_result_failure: + data.transferStatus = UnknownFailure; + break; + case tego_file_transfer_result_cancelled: + data.transferStatus = Cancelled; + break; + case tego_file_transfer_result_rejected: + data.transferStatus = Rejected; + break; + case tego_file_transfer_result_bad_hash: + data.transferStatus = BadFileHash; + break; + case tego_file_transfer_result_network_error: + data.transferStatus = NetworkError; + break; + case tego_file_transfer_result_filesystem_error: + data.transferStatus = FileSystemError; + break; + default: + data.transferStatus = InvalidTransfer; + break; + } + emitDataChanged(row); + this->addEventFromMessage(row); + } + } + + void ConversationModel::clear() + { + if (messages.isEmpty()) + { + return; + } + + beginRemoveRows(QModelIndex(), 0, messages.size()-1); + messages.clear(); + endRemoveRows(); + + resetUnreadCount(); + } + + void ConversationModel::messageReceived(tego_message_id_t messageId, QDateTime timestamp, const QString& text) + { + MessageData md; + md.type = TextMessage; + md.text = text; + md.time = timestamp; + md.identifier = messageId; + md.status = Received; + + this->beginInsertRows(QModelIndex(), 0, 0); + this->messages.prepend(std::move(md)); + this->endInsertRows(); + + this->setUnreadCount(this->unreadCount + 1); + this->addEventFromMessage(indexOfIncomingMessage(messageId)); + } + + void ConversationModel::messageAcknowledged(tego_message_id_t messageId, bool accepted) + { + auto row = this->indexOfOutgoingMessage(messageId); + Q_ASSERT(row >= 0); + + MessageData &data = messages[row]; + data.status = accepted ? Delivered : Error; + emitDataChanged(row); + } + + void ConversationModel::addEventFromMessage(int row) + { + EventData ed; + + if (row < 0) + return; + + auto &md = this->messages[row]; + switch (md.type) + { + case TextMessage: + ed.type = TextMessageEvent; + ed.messageData.reverseIndex = static_cast(this->messages.size() - row); + break; + case TransferMessage: + ed.type = TransferMessageEvent; + ed.transferData.reverseIndex = static_cast(this->messages.size() - row); + ed.transferData.status = md.transferStatus; + ed.transferData.bytesTransferred = safe_cast(md.bytesTransferred); + break; + default: + return; + } + ed.time = QDateTime::currentDateTime(); + + this->events.append(std::move(ed)); + emit this->conversationEventCountChanged(); + } + + void ConversationModel::setStatus(ContactUser::Status status) + { + EventData ed; + + ed.type = UserStatusUpdateEvent; + ed.userStatusData.status = status; + ed.userStatusData.target = UserTargetPeer; + ed.time = QDateTime::currentDateTime(); + + this->events.append(std::move(ed)); + emit this->conversationEventCountChanged(); + } + + void ConversationModel::emitDataChanged(int row) + { + Q_ASSERT(row >= 0); + emit dataChanged(index(row, 0), index(row, 0)); + } + + int ConversationModel::indexOfMessage(quint32 identifier) const + { + for (int i = 0; i < messages.size(); i++) { + const auto& currentMessage = messages[i]; + + if (currentMessage.identifier == identifier) + return i; + } + return -1; + } + + int ConversationModel::indexOfOutgoingMessage(quint32 identifier) const + { + for (int i = 0; i < messages.size(); i++) { + const auto& currentMessage = messages[i]; + + if (currentMessage.identifier == identifier && (currentMessage.status != Received)) + return i; + } + return -1; + } + + int ConversationModel::indexOfIncomingMessage(quint32 identifier) const + { + for (int i = 0; i < messages.size(); i++) { + const auto& currentMessage = messages[i]; + + if (currentMessage.identifier == identifier && (currentMessage.status == Received)) + return i; + } + return -1; + } + + const char* ConversationModel::getMessageStatusString(const MessageStatus status) + { + constexpr static const char* statusList[] = + { + "None", + "Received", + "Queued", + "Sending", + "Delivered", + "Error" + }; + + return statusList[static_cast(status)]; + } + + const char* ConversationModel::getTransferStatusString(const TransferStatus status) + { + constexpr static const char* statusList[] = + { + "Invalid Transfer", + "Pending", + "Accepted", + "Rejected", + "In Progress", + "Cancelled", + "Finished", + "Unknown Failure", + "Bad File Hash", + "Network Error", + "Filesystem Error" + }; + + return statusList[static_cast(status)]; + } +} diff --git a/src/libtego_ui/shims/ConversationModel.h b/src/libtego_ui/shims/ConversationModel.h new file mode 100644 index 00000000..187296a4 --- /dev/null +++ b/src/libtego_ui/shims/ConversationModel.h @@ -0,0 +1,190 @@ +#pragma once + +#include "ContactUser.h" + +namespace shims +{ + class ContactUser; + class ConversationModel : public QAbstractListModel + { + Q_OBJECT + Q_ENUMS(MessageStatus) + + Q_PROPERTY(shims::ContactUser* contact READ contact WRITE setContact NOTIFY contactChanged) + Q_PROPERTY(int unreadCount READ getUnreadCount RESET resetUnreadCount NOTIFY unreadCountChanged) + Q_PROPERTY(int conversationEventCount READ getConversationEventCount NOTIFY conversationEventCountChanged) + public: + ConversationModel(QObject *parent = 0); + + enum { + TimestampRole = Qt::UserRole, + IsOutgoingRole, + StatusRole, + SectionRole, + TimespanRole, + TypeRole, + TransferRole, + }; + + enum MessageStatus { + None, + Received, + Queued, + Sending, + Delivered, + Error + }; + + enum MessageDataType + { + InvalidMessage = -1, + TextMessage, + TransferMessage, + }; + + enum TransferStatus + { + InvalidTransfer, + Pending, + Accepted, + Rejected, + InProgress, + Cancelled, + Finished, + UnknownFailure, + BadFileHash, + NetworkError, + FileSystemError, + }; + Q_ENUM(TransferStatus); + + enum TransferDirection + { + InvalidDirection, + Uploading, + Downloading, + }; + Q_ENUM(TransferDirection); + + enum EventType { + InvalidEvent, + TextMessageEvent, + TransferMessageEvent, + UserStatusUpdateEvent + }; + + enum UserStatusTarget { + UserTargetNone, + UserTargetClient, + UserTargetPeer + }; + + // impl QAbstractListModel + virtual QHash roleNames() const; + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + + shims::ContactUser *contact() const; + void setContact(shims::ContactUser *contact); + int getUnreadCount() const; + Q_INVOKABLE void resetUnreadCount(); + + void sendFile(); + bool hasEventsToExport(); + Q_INVOKABLE int getConversationEventCount() const { return this->events.size(); } + bool exportConversation(); + // invokable function neeeds to use a Qt type since it is invokable from QML + static_assert(std::is_same_v); + Q_INVOKABLE void tryAcceptFileTransfer(quint32 id); + Q_INVOKABLE void cancelFileTransfer(quint32 id); + Q_INVOKABLE void rejectFileTransfer(quint32 id); + + + void setStatus(ContactUser::Status status); + + void fileTransferRequestReceived(tego_file_transfer_id_t id, QString fileName, QString fileHash, quint64 fileSize); + void fileTransferRequestAcknowledged(tego_file_transfer_id_t id, bool accepted); + void fileTransferRequestResponded(tego_file_transfer_id_t id, tego_file_transfer_response_t response); + void fileTransferRequestProgressUpdated(tego_file_transfer_id_t id, quint64 bytesTransferred); + void fileTransferRequestCompleted(tego_file_transfer_id_t id, tego_file_transfer_result_t result); + + void messageReceived(tego_message_id_t messageId, QDateTime timestamp, const QString& text); + void messageAcknowledged(tego_message_id_t messageId, bool accepted); + + public slots: + void sendMessage(const QString &text); + void clear(); + + signals: + void contactChanged(); + void unreadCountChanged(int prevCount, int currentCount); + void conversationEventCountChanged(); + private: + void setUnreadCount(int count); + + shims::ContactUser* contactUser = nullptr; + + struct MessageData + { + MessageDataType type = InvalidMessage; + QString text = {}; + QDateTime time = {}; + static_assert(std::is_same_v); + static_assert(std::is_same_v); + quint32 identifier = 0; + MessageStatus status = None; + quint8 attemptCount = 0; + // file transfer data + QString fileName = {}; + qint64 fileSize = 0; + QString fileHash = {}; + quint64 bytesTransferred = 0; + TransferDirection transferDirection = InvalidDirection; + TransferStatus transferStatus = InvalidTransfer; + }; + + struct EventData + { + EventType type = InvalidEvent; + union { + struct { + size_t reverseIndex = 0; + } messageData; + struct { + size_t reverseIndex = 0; + TransferStatus status = InvalidTransfer; + qint64 bytesTransferred = 0; // we care about this for when a transfer is cancelled midway + } transferData; + struct { + ContactUser::Status status = ContactUser::Status::Offline; + UserStatusTarget target = UserTargetNone; // when the protocol is eventually fixed and users + // are notified of being blocked, this will be needed + } userStatusData; + }; + QDateTime time = {}; + + EventData() {} + }; + + QList messages; + QList events; + + void addEventFromMessage(int row); + + void deserializeTextMessageEventToFile(const EventData &event, std::ofstream &ofile) const; + void deserializeTransferMessageEventToFile(const EventData &event, std::ofstream &ofile) const; + void deserializeUserStatusUpdateEventToFile(const EventData &event, std::ofstream &ofile) const; + void deserializeEventToFile(const EventData &event, std::ofstream &ofile) const; + + int unreadCount = 0; + + void emitDataChanged(int row); + + int indexOfMessage(quint32 identifier) const; + int indexOfOutgoingMessage(quint32 identifier) const; + int indexOfIncomingMessage(quint32 identifier) const; + + static const char* getMessageStatusString(const MessageStatus status); + static const char* getTransferStatusString(const TransferStatus status); + }; +} diff --git a/src/libtego_ui/shims/IncomingContactRequest.cpp b/src/libtego_ui/shims/IncomingContactRequest.cpp new file mode 100644 index 00000000..e3e71928 --- /dev/null +++ b/src/libtego_ui/shims/IncomingContactRequest.cpp @@ -0,0 +1,68 @@ +#include "IncomingContactRequest.h" +#include "UserIdentity.h" + +namespace shims +{ + IncomingContactRequest::IncomingContactRequest(const QString& hostname, const QString& msg) + : serviceIdString(hostname.chopped(tego::static_strlen(".onion"))) + , nickname() + , message(msg) + , userId() + { + auto serviceIdRaw = serviceIdString.toUtf8(); + + std::unique_ptr serviceId; + tego_v3_onion_service_id_from_string(tego::out(serviceId), serviceIdRaw.data(), static_cast(serviceIdRaw.size()), tego::throw_on_error()); + tego_user_id_from_v3_onion_service_id(tego::out(userId), serviceId.get(), tego::throw_on_error()); + + // save our request to disk + SettingsObject settings(QString("users.%1").arg(serviceIdString)); + settings.write("type", "requesting"); + } + + QString IncomingContactRequest::getHostname() const + { + return serviceIdString + QString(".onion"); + } + + QString IncomingContactRequest::getContactId() const + { + return QString("ricochet:") + serviceIdString; + } + + void IncomingContactRequest::setNickname(const QString& newNickname) + { + logger::println("setNickname : '{}'", newNickname); + this->nickname = newNickname; + emit this->nicknameChanged(); + } + + void IncomingContactRequest::accept() + { + auto userIdentity = shims::UserIdentity::userIdentity; + auto context = userIdentity->getContext(); + auto contactManager = userIdentity->getContacts(); + + tego_context_acknowledge_chat_request(context, userId.get(), tego_chat_acknowledge_accept, tego::throw_on_error()); + + userIdentity->removeIncomingContactRequest(this); + + contactManager->addContact(serviceIdString, nickname); + + SettingsObject settings(QString("users.%1").arg(serviceIdString)); + settings.write("type", "allowed"); + } + + void IncomingContactRequest::reject() + { + auto userIdentity = shims::UserIdentity::userIdentity; + auto context = userIdentity->getContext(); + + tego_context_acknowledge_chat_request(context, userId.get(), tego_chat_acknowledge_block, tego::throw_on_error()); + + userIdentity->removeIncomingContactRequest(this); + + SettingsObject settings(QString("users.%1").arg(serviceIdString)); + settings.write("type", "blocked"); + } +} diff --git a/src/libtego_ui/shims/IncomingContactRequest.h b/src/libtego_ui/shims/IncomingContactRequest.h new file mode 100644 index 00000000..7539416f --- /dev/null +++ b/src/libtego_ui/shims/IncomingContactRequest.h @@ -0,0 +1,35 @@ +#pragma once + +namespace shims +{ + class IncomingContactRequest : public QObject + { + Q_OBJECT + Q_DISABLE_COPY(IncomingContactRequest) + + Q_PROPERTY(QString hostname READ getHostname CONSTANT) + Q_PROPERTY(QString nickname READ getNickname WRITE setNickname NOTIFY nicknameChanged) + Q_PROPERTY(QString contactId READ getContactId CONSTANT) + Q_PROPERTY(QString message READ getMessage CONSTANT) + public: + IncomingContactRequest(const QString& hostname, const QString& message); + + QString getHostname() const; + QString getNickname() const { return nickname; } + void setNickname(const QString&); + QString getContactId() const; + QString getMessage() const { return message; } + + public slots: + void accept(); + void reject(); + signals: + void nicknameChanged(); + + private: + const QString serviceIdString; + QString nickname; + const QString message; + std::unique_ptr userId; + }; +} \ No newline at end of file diff --git a/src/libtego_ui/shims/OutgoingContactRequest.cpp b/src/libtego_ui/shims/OutgoingContactRequest.cpp new file mode 100644 index 00000000..8cab7066 --- /dev/null +++ b/src/libtego_ui/shims/OutgoingContactRequest.cpp @@ -0,0 +1,33 @@ +#include "OutgoingContactRequest.h" + +namespace shims +{ + OutgoingContactRequest::Status OutgoingContactRequest::getStatus() const + { + logger::trace(); + return this->status; + } + + void OutgoingContactRequest::setStatus(Status newStatus) + { + if (this->status != newStatus) + { + emit this->statusChanged(this->status, newStatus); + this->status = newStatus; + } + } + + void OutgoingContactRequest::setAccepted() + { + this->setStatus(Acknowledged); + this->setStatus(Accepted); + } + + void OutgoingContactRequest::setRejected() + { + this->setStatus(Acknowledged); + this->setStatus(Rejected); + + emit this->rejected(); + } +} diff --git a/src/libtego_ui/shims/OutgoingContactRequest.h b/src/libtego_ui/shims/OutgoingContactRequest.h new file mode 100644 index 00000000..7e681cdf --- /dev/null +++ b/src/libtego_ui/shims/OutgoingContactRequest.h @@ -0,0 +1,41 @@ +#pragma once + +namespace shims +{ + class ContactUser; + class OutgoingContactRequest : public QObject + { + Q_OBJECT + Q_DISABLE_COPY(OutgoingContactRequest) + Q_ENUMS(Status) + + Q_PROPERTY(Status status READ getStatus NOTIFY statusChanged) + public: + enum Status + { + Pending, + Acknowledged, + Accepted, + Error, + Rejected, + FirstResult = Accepted + }; + + OutgoingContactRequest() = default; + + Status getStatus() const; + + void setStatus(Status status); + + void setAccepted(); + void setRejected(); + + + signals: + void statusChanged(int newStatus, int oldStatus); + void rejected(); + + private: + Status status = shims::OutgoingContactRequest::Pending; + }; +} \ No newline at end of file diff --git a/src/libtego_ui/shims/TorCommand.cpp b/src/libtego_ui/shims/TorCommand.cpp new file mode 100644 index 00000000..71dbf7d1 --- /dev/null +++ b/src/libtego_ui/shims/TorCommand.cpp @@ -0,0 +1,16 @@ +#include "TorCommand.h" + +namespace shims +{ + void TorControlCommand::onFinished(bool success) + { + this->m_successful = success; + emit this->finished(); + this->deleteLater(); + } + + bool TorControlCommand::isSuccessful() const + { + return m_successful; + } +} \ No newline at end of file diff --git a/src/libtego_ui/shims/TorCommand.h b/src/libtego_ui/shims/TorCommand.h new file mode 100644 index 00000000..1dd84a5e --- /dev/null +++ b/src/libtego_ui/shims/TorCommand.h @@ -0,0 +1,23 @@ +#pragma once + +namespace shims +{ + class TorControlCommand : public QObject + { + Q_OBJECT + Q_DISABLE_COPY(TorControlCommand) + + Q_PROPERTY(bool successful READ isSuccessful CONSTANT) + public: + TorControlCommand() = default; + void onFinished(bool success); + + bool isSuccessful() const; + + signals: + void finished(); + + private: + bool m_successful = false; + }; +} \ No newline at end of file diff --git a/src/libtego_ui/shims/TorControl.cpp b/src/libtego_ui/shims/TorControl.cpp new file mode 100644 index 00000000..5e5c9bd9 --- /dev/null +++ b/src/libtego_ui/shims/TorControl.cpp @@ -0,0 +1,304 @@ +#include "TorControl.h" + +namespace shims +{ + TorControl* TorControl::torControl = nullptr; + + TorControl::TorControl(tego_context_t* context_) + : context(context_) + { } + + QObject* TorControl::setConfiguration(const QVariantMap &options) + { + logger::trace(); + Q_ASSERT(this->m_setConfigurationCommand == nullptr); + + // create command shim yuck + auto setConfigurationCommand = new TorControlCommand(); + QQmlEngine::setObjectOwnership(setConfigurationCommand, QQmlEngine::CppOwnership); + + this->m_setConfigurationCommand = setConfigurationCommand; + + std::unique_ptr daemonConfig; + tego_tor_daemon_config_initialize( + tego::out(daemonConfig), + tego::throw_on_error()); + + // + // see TorConfigurationPage.xml + // + + // disable network + if (auto it = options.find("disableNetwork"); it != options.end()) + { + const auto disableNetwork = options.value("disableNetwork").toInt(); + Q_ASSERT(disableNetwork == TEGO_FALSE || disableNetwork == TEGO_TRUE); + + tego_tor_daemon_config_set_disable_network( + daemonConfig.get(), + static_cast(disableNetwork), + tego::throw_on_error()); + } + + // proxy + if (auto it = options.find("proxyType"); it != options.end() && *it != "") + { + Q_ASSERT(options.contains("proxyAddress")); + Q_ASSERT(options.contains("proxyPort")); + + // we always need these params + const auto proxyAddress = options.value("proxyAddress").toString().toUtf8(); + const auto proxyPort = options.value("proxyPort").toInt(); + + // ensure vali proxy type + const auto& proxyType = *it; + Q_ASSERT(proxyType == "socks4" || proxyType == "socks5" || proxyType == "https"); + + // handle socks4 + if (proxyType == "socks4") { + + Q_ASSERT(proxyPort > 0 && proxyPort < 65536); + + tego_tor_daemon_config_set_proxy_socks4( + daemonConfig.get(), + proxyAddress.data(), + static_cast(proxyAddress.size()), + static_cast(proxyPort), + tego::throw_on_error()); + } + // handle socks5 and https + else if (proxyType == "socks5" || proxyType == "https") + { + Q_ASSERT(options.contains("proxyUsername")); + Q_ASSERT(options.contains("proxyPassword")); + + const auto proxyUsername = options.value("proxyUsername").toString().toUtf8(); + const auto proxyPassword = options.value("proxyPassword").toString().toUtf8(); + + if (proxyType == "socks5") + { + tego_tor_daemon_config_set_proxy_socks5( + daemonConfig.get(), + proxyAddress.data(), + static_cast(proxyAddress.size()), + static_cast(proxyPort), + proxyUsername.data(), + static_cast(proxyUsername.size()), + proxyPassword.data(), + static_cast(proxyPassword.size()), + tego::throw_on_error()); + } + else if (proxyType == "https") + { + tego_tor_daemon_config_set_proxy_https( + daemonConfig.get(), + proxyAddress.data(), + static_cast(proxyAddress.size()), + static_cast(proxyPort), + proxyUsername.data(), + static_cast(proxyUsername.size()), + proxyPassword.data(), + static_cast(proxyPassword.size()), + tego::throw_on_error()); + } + } + } + + // allowed ports + if (auto it = options.find("allowedPorts"); it != options.end()) + { + auto portList = it->toList(); + + if (portList.size() > 0) + { + std::vector ports; + ports.reserve(static_cast(portList.size())); + + // convert port list + for(const auto& v : portList) + { + auto currentPort = v.toInt(); + Q_ASSERT(currentPort > 0 && currentPort < 65536); + ports.push_back(static_cast(currentPort)); + } + + tego_tor_daemon_config_set_allowed_ports( + daemonConfig.get(), + ports.data(), + ports.size(), + tego::throw_on_error()); + } + } + + // bridges + if (auto it = options.find("bridges"); it != options.end()) + { + // get the bridge list + auto bridgeList = it->toList(); + + if (bridgeList.size() > 0) + { + // convert the bridges to strings + std::vector bridgeStrings; + bridgeStrings.reserve(static_cast(bridgeList.size())); + + // create a vector of said strings, so that the raw pointers have a lifetime for the rest of this scope + for(auto& v : bridgeList) + { + auto currentBridge = v.toString(); + bridgeStrings.push_back(currentBridge.toStdString()); + } + const size_t bridgeCount = bridgeStrings.size(); + + // copy over raw + auto rawBridges = std::make_unique(bridgeCount); + auto rawBridgeLengths = std::make_unique(bridgeCount); + + std::fill(rawBridges.get(), rawBridges.get() + bridgeCount, nullptr); + std::fill(rawBridgeLengths.get(), rawBridgeLengths.get()+ bridgeCount, 0); + + for(size_t i = 0; i < bridgeStrings.size(); ++i) + { + const auto& currentBridgeString = bridgeStrings[i]; + rawBridges[i] = const_cast(currentBridgeString.data()); + rawBridgeLengths[i] = currentBridgeString.size(); + } + + tego_tor_daemon_config_set_bridges( + daemonConfig.get(), + const_cast(rawBridges.get()), + rawBridgeLengths.get(), + bridgeCount, + tego::throw_on_error()); + } + } + + tego_context_update_tor_daemon_config( + context, + daemonConfig.get(), + tego::throw_on_error()); + + return this->m_setConfigurationCommand; + } + + // for now we just assume we always have ownership, + // as we have no way in config to setup usage of + // an existing tor process + bool TorControl::hasOwnership() const + { + logger::trace(); + return true; + } + + void TorControl::saveConfiguration() + { + logger::trace(); + tego_context_save_tor_daemon_config( + context, + tego::throw_on_error()); + } + + QString TorControl::torVersion() const + { + logger::trace(); + return tego_context_get_tor_version_string( + context, + tego::throw_on_error()); + } + + TorControl::Status TorControl::status() const + { + tego_tor_control_status_t status; + tego_context_get_tor_control_status( + context, + &status, + tego::throw_on_error()); + + logger::trace(); + return static_cast(status); + } + + TorControl::TorStatus TorControl::torStatus() const + { + tego_tor_network_status_t status; + tego_context_get_tor_network_status( + context, + &status, + tego::throw_on_error()); + + logger::trace(); + switch(status) + { + case tego_tor_network_status_unknown: + return TorControl::TorUnknown; + case tego_tor_network_status_ready: + return TorControl::TorReady; + case tego_tor_network_status_offline: + return TorControl::TorOffline; + default: + return TorControl::TorError; + } + } + + QVariantMap TorControl::bootstrapStatus() const + { + QVariantMap retval; + try + { + int32_t progress; + tego_tor_bootstrap_tag_t tag; + tego_context_get_tor_bootstrap_status( + context, + &progress, + &tag, + tego::throw_on_error()); + + auto tagSummary = tego_tor_bootstrap_tag_to_summary( + tag, + tego::throw_on_error()); + + retval["severity"] = "mock severity"; + retval["progress"] = progress; + retval["tag"] = (tag == tego_tor_bootstrap_tag_done) ? "done" : "mock_tag"; + retval["summary"] = QString(tagSummary); + } + catch(const std::exception& ex) + { + logger::println("Exception thrown : {}", ex.what()); + } + return retval; + } + + QString TorControl::errorMessage() const + { + return m_errorMessage; + } + + void TorControl::setStatus(Status status) + { + auto oldStatus = m_status; + if (oldStatus == status) return; + + m_status = status; + emit this->statusChanged( + static_cast(status), + static_cast(oldStatus)); + } + + void TorControl::setTorStatus(TorStatus status) + { + auto oldStatus = m_torStatus; + if (oldStatus == status) return; + + m_torStatus = status; + emit this->torStatusChanged( + static_cast(status), + static_cast(oldStatus)); + } + + void TorControl::setErrorMessage(const QString& msg) + { + m_errorMessage = msg; + this->setStatus(TorControl::Error); + } +} diff --git a/src/libtego_ui/shims/TorControl.h b/src/libtego_ui/shims/TorControl.h new file mode 100644 index 00000000..66f73d95 --- /dev/null +++ b/src/libtego_ui/shims/TorControl.h @@ -0,0 +1,73 @@ +#pragma once + +#include "TorCommand.h" + +namespace shims +{ + // shim version of Tor::ToControl with just the functionality requried by the UI + class TorControl : public QObject + { + Q_OBJECT + Q_ENUMS(Status TorStatus) + + Q_PROPERTY(bool hasOwnership READ hasOwnership CONSTANT) + Q_PROPERTY(QString torVersion READ torVersion CONSTANT) + // Status of the control connection + Q_PROPERTY(Status status READ status NOTIFY statusChanged) + // Status of Tor (and whether it believes it can connect) + Q_PROPERTY(TorStatus torStatus READ torStatus NOTIFY torStatusChanged) + Q_PROPERTY(QVariantMap bootstrapStatus READ bootstrapStatus NOTIFY bootstrapStatusChanged) + // uses statusChanged like actual backend implementation + Q_PROPERTY(QString errorMessage READ errorMessage NOTIFY statusChanged) + public: + enum Status + { + Error = -1, + NotConnected, + Connecting, + Authenticating, + Connected + }; + + enum TorStatus + { + TorError = -1, + TorUnknown, + TorOffline, + TorReady + }; + + Q_INVOKABLE QObject *setConfiguration(const QVariantMap &options); + Q_INVOKABLE void saveConfiguration(); + + TorControl(tego_context_t* context); + + /* Ownership means that tor is managed by this socket, and we + * can shut it down, own its configuration, etc. */ + bool hasOwnership() const; + + QString torVersion() const; + Status status() const; + TorStatus torStatus() const; + QVariantMap bootstrapStatus() const; + QString errorMessage() const; + + void setStatus(Status); + void setTorStatus(TorStatus); + void setErrorMessage(const QString&); + + static TorControl* torControl; + TorControlCommand* m_setConfigurationCommand = nullptr; + Status m_status = NotConnected; + TorStatus m_torStatus = TorUnknown; + QString m_errorMessage; + + signals: + void statusChanged(int newStatus, int oldStatus); + void torStatusChanged(int newStatus, int oldStatus); + void bootstrapStatusChanged(); + + private: + tego_context_t* context; + }; +} \ No newline at end of file diff --git a/src/libtego_ui/shims/TorManager.cpp b/src/libtego_ui/shims/TorManager.cpp new file mode 100644 index 00000000..e63e6be3 --- /dev/null +++ b/src/libtego_ui/shims/TorManager.cpp @@ -0,0 +1,76 @@ +#include "TorManager.h" +#include "utils/Useful.h" + +namespace shims +{ + TorManager* TorManager::torManager = nullptr; + + TorManager::TorManager(tego_context_t* context) + : m_context(context) + { + // this ultimately controls whether the config dialog appears on launch + if (this->configurationNeeded()) + { + emit this->configurationNeededChanged(); + } + } + + bool TorManager::configurationNeeded() const + { + logger::trace(); + + tego_bool_t daemonConfigured = TEGO_FALSE; + tego_context_get_tor_daemon_configured( + m_context, + &daemonConfigured, + tego::throw_on_error()); + + return (daemonConfigured == TEGO_FALSE); + } + + QStringList TorManager::logMessages() const + { + const auto bufferSize = tego_context_get_tor_logs_size( + m_context, + tego::throw_on_error()); + auto buffer = std::make_unique(bufferSize); + + // NOTE: it is possible for a new log entry to have been received in-between + // getting the required buffer size and the data + + const auto written = tego_context_get_tor_logs( + m_context, + buffer.get(), + bufferSize, + tego::throw_on_error()); + + return QString::fromUtf8(buffer.get(), safe_cast(written)).split('\n'); + } + + QString TorManager::running() const + { + return this->m_running; + } + + void TorManager::setRunning(const QString& running) + { + this->m_running = running; + emit this->runningChanged(); + } + + bool TorManager::hasError() const + { + return !this->m_errorMessage.isEmpty(); + } + + QString TorManager::errorMessage() const + { + return this->m_errorMessage; + } + + void TorManager::setErrorMessage(const QString& msg) + { + this->m_errorMessage = msg; + emit this->errorChanged(); + } +} diff --git a/src/libtego_ui/shims/TorManager.h b/src/libtego_ui/shims/TorManager.h new file mode 100644 index 00000000..43ea49b2 --- /dev/null +++ b/src/libtego_ui/shims/TorManager.h @@ -0,0 +1,37 @@ +#pragma once + +namespace shims +{ + class TorManager : public QObject + { + Q_OBJECT + + Q_PROPERTY(bool configurationNeeded READ configurationNeeded NOTIFY configurationNeededChanged) + Q_PROPERTY(QStringList logMessages READ logMessages CONSTANT) + Q_PROPERTY(QString running READ running NOTIFY runningChanged) + Q_PROPERTY(bool hasError READ hasError NOTIFY errorChanged) + Q_PROPERTY(QString errorMessage READ errorMessage NOTIFY errorChanged) + + public: + TorManager(tego_context_t*); + static TorManager* torManager; + + bool configurationNeeded() const; + QStringList logMessages() const; + QString running() const; + void setRunning(const QString& running); + bool hasError() const; + QString errorMessage() const; + void setErrorMessage(const QString& message); + signals: + void configurationNeededChanged(); + void logMessage(const QString &message); + void runningChanged(); + void errorChanged(); + + private: + tego_context_t* m_context; + QString m_errorMessage; + QString m_running; + }; +} \ No newline at end of file diff --git a/src/libtego_ui/shims/UserIdentity.cpp b/src/libtego_ui/shims/UserIdentity.cpp new file mode 100644 index 00000000..44104795 --- /dev/null +++ b/src/libtego_ui/shims/UserIdentity.cpp @@ -0,0 +1,87 @@ +#include "ContactsManager.h" +#include "UserIdentity.h" +#include "IncomingContactRequest.h" + +shims::UserIdentity* shims::UserIdentity::userIdentity = nullptr; + +namespace shims +{ + UserIdentity::UserIdentity(tego_context_t* context_) + : contacts(context_) + , context(context_) + , online(false) + + { } + + void UserIdentity::createIncomingContactRequest(const QString& hostname, const QString& message) + { + auto incomingContactRequest = new shims::IncomingContactRequest(hostname, message); + this->requests.push_back(incomingContactRequest); + + emit this->requestAdded(incomingContactRequest); + emit this->requestsChanged(); + } + + void UserIdentity::removeIncomingContactRequest(shims::IncomingContactRequest* incomingContactRequest) + { + auto it = std::find(this->requests.begin(), this->requests.end(), incomingContactRequest); + Q_ASSERT(it != this->requests.end()); + + this->requests.erase(it); + emit this->requestsChanged(); + + incomingContactRequest->deleteLater(); + } + + QList UserIdentity::getRequests() const + { + logger::trace(); + QList retval; + retval.reserve(requests.size()); + for(auto currentRequest: requests) + { + retval.push_back(currentRequest); + } + return retval; + } + + bool UserIdentity::isServiceOnline() const + { + logger::trace(); + + auto state = tego_host_user_state_unknown; + tego_context_get_host_user_state(this->context, &state, tego::throw_on_error()); + + return state == tego_host_user_state_online; + } + + QString UserIdentity::contactID() const + { + // get host user id and convert to the ricochet:blahlah format + std::unique_ptr userId; + tego_context_get_host_user_id(this->context, tego::out(userId), tego::throw_on_error()); + + std::unique_ptr serviceId; + tego_user_id_get_v3_onion_service_id(userId.get(), tego::out(serviceId), tego::throw_on_error()); + + char serviceIdString[TEGO_V3_ONION_SERVICE_ID_SIZE] = {0}; + tego_v3_onion_service_id_to_string(serviceId.get(), serviceIdString, sizeof(serviceIdString), tego::throw_on_error()); + + QString contactId; + QTextStream(&contactId) << "ricochet:" << serviceIdString; + + return contactId; + } + + shims::ContactsManager* UserIdentity::getContacts() + { + logger::trace(); + return &contacts; + } + + void UserIdentity::setOnline(bool isOnline) + { + this->online = isOnline; + emit this->statusChanged(); + } +} diff --git a/src/libtego_ui/shims/UserIdentity.h b/src/libtego_ui/shims/UserIdentity.h new file mode 100644 index 00000000..eb9f34db --- /dev/null +++ b/src/libtego_ui/shims/UserIdentity.h @@ -0,0 +1,51 @@ +#pragma once + +#include "ContactsManager.h" +namespace shims +{ + class IncomingContactRequest; + class UserIdentity : public QObject + { + Q_OBJECT + Q_DISABLE_COPY(UserIdentity) + + // needed by createDialog("ContactRequestDialog.qml",...) in main.qml + Q_PROPERTY(QList requests READ getRequests NOTIFY requestsChanged) + // used in TorPreferences.qml + Q_PROPERTY(bool isOnline READ isServiceOnline NOTIFY statusChanged) + // this originally had a contactIDChanged signal + Q_PROPERTY(QString contactID READ contactID CONSTANT) + // needed in MainWindow.qml + Q_PROPERTY(shims::ContactsManager *contacts READ getContacts CONSTANT) + public: + UserIdentity(tego_context_t* context); + + void createIncomingContactRequest(const QString& hostname, const QString& message); + void removeIncomingContactRequest(shims::IncomingContactRequest* incomingContactRequest); + QList getRequests() const; + bool isServiceOnline() const; + QString contactID() const; + shims::ContactsManager* getContacts(); + + void setOnline(bool); + + static shims::UserIdentity* userIdentity; + shims::ContactsManager contacts; + + tego_context_t* getContext() { return context; } + signals: + void statusChanged(); + // used in main.qml + void requestAdded(shims::IncomingContactRequest *request); + void requestsChanged(); + // used in MainWindow.qml + void unreadCountChanged(ContactUser *user, int unreadCount); + void contactStatusChanged(ContactUser* user, int status); + + private: + QList requests; + + tego_context_t *context; + bool online; + }; +} \ No newline at end of file diff --git a/src/ui/LinkedText.cpp b/src/libtego_ui/ui/Clipboard.cpp similarity index 53% rename from src/ui/LinkedText.cpp rename to src/libtego_ui/ui/Clipboard.cpp index ec9ae68f..0da4580a 100644 --- a/src/ui/LinkedText.cpp +++ b/src/libtego_ui/ui/Clipboard.cpp @@ -30,51 +30,9 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "LinkedText.h" -#include -#include -#include -#include -#include +#include "Clipboard.h" -LinkedText::LinkedText(QObject *parent) - : QObject(parent) -{ - // Select things that look like URLs of some kind and allow QUrl::fromUserInput to validate them - linkRegex = QRegularExpression(QStringLiteral("([a-z]{3,9}:|www\\.)([^\\s,.);!>]|[,.);!>](?!\\s|$))+"), QRegularExpression::CaseInsensitiveOption); - - allowedSchemes << QStringLiteral("http") - << QStringLiteral("https") - << QStringLiteral("torsion") - << QStringLiteral("ricochet"); -} - -QString LinkedText::parsed(const QString &input) -{ - QString re; - int p = 0; - QRegularExpressionMatchIterator it = linkRegex.globalMatch(input); - while (it.hasNext()) { - QRegularExpressionMatch match = it.next(); - int start = match.capturedStart(); - - QUrl url = QUrl::fromUserInput(match.capturedRef().toString()); - if (!allowedSchemes.contains(url.scheme().toLower())) - continue; - - if (start > p) - re.append(input.mid(p, start - p).toHtmlEscaped().replace(QLatin1Char('\n'), QStringLiteral("
"))); - re.append(QStringLiteral("%2").arg(QString::fromLatin1(url.toEncoded()).toHtmlEscaped(), match.capturedRef().toString().toHtmlEscaped())); - p = match.capturedEnd(); - } - - if (p < input.size()) - re.append(input.mid(p).toHtmlEscaped().replace(QLatin1Char('\n'), QStringLiteral("
"))); - - return re; -} - -void LinkedText::copyToClipboard(const QString &text) +void Clipboard::copyText(QString const& text) { QClipboard *clipboard = qApp->clipboard(); clipboard->setText(text); @@ -83,3 +41,6 @@ void LinkedText::copyToClipboard(const QString &text) clipboard->setText(text, QClipboard::Selection); } +QObject* Clipboard::singleton_provider(QQmlEngine*,QJSEngine*) { + return new Clipboard(); +} \ No newline at end of file diff --git a/src/ui/LinkedText.h b/src/libtego_ui/ui/Clipboard.h similarity index 83% rename from src/ui/LinkedText.h rename to src/libtego_ui/ui/Clipboard.h index deb7475a..b3b36ee7 100644 --- a/src/ui/LinkedText.h +++ b/src/libtego_ui/ui/Clipboard.h @@ -33,23 +33,16 @@ #ifndef LINKEDTEXT_H #define LINKEDTEXT_H -#include -#include - -class LinkedText : public QObject +class Clipboard : public QObject { Q_OBJECT - Q_DISABLE_COPY(LinkedText) - + Q_DISABLE_COPY(Clipboard) public: - explicit LinkedText(QObject *parent = 0); + explicit Clipboard(QObject* parent = nullptr) : QObject(parent) {} - Q_INVOKABLE QString parsed(const QString &input); - Q_INVOKABLE void copyToClipboard(const QString &text); + Q_INVOKABLE void copyText(QString const& text); -private: - QRegularExpression linkRegex; - QStringList allowedSchemes; + static QObject* singleton_provider(QQmlEngine*,QJSEngine*); }; #endif diff --git a/src/ui/ContactsModel.cpp b/src/libtego_ui/ui/ContactsModel.cpp similarity index 70% rename from src/ui/ContactsModel.cpp rename to src/libtego_ui/ui/ContactsModel.cpp index 6a9a8782..01a18352 100644 --- a/src/ui/ContactsModel.cpp +++ b/src/libtego_ui/ui/ContactsModel.cpp @@ -31,30 +31,32 @@ */ #include "ContactsModel.h" -#include "core/IdentityManager.h" -#include "core/ContactsManager.h" -#include -inline bool contactSort(const ContactUser *c1, const ContactUser *c2) +#include "shims/ContactsManager.h" +#include "shims/ContactUser.h" +#include "shims/UserIdentity.h" + +inline bool contactSort(const shims::ContactUser *c1, const shims::ContactUser *c2) { - if (c1->status() != c2->status()) - return c1->status() < c2->status(); - return c1->nickname().localeAwareCompare(c2->nickname()) < 0; + if (c1->getStatus() != c2->getStatus()) + return c1->getStatus() < c2->getStatus(); + return c1->getNickname().localeAwareCompare(c2->getNickname()) < 0; } ContactsModel::ContactsModel(QObject *parent) - : QAbstractListModel(parent), m_identity(0) + : QAbstractListModel(parent) + , m_identity(shims::UserIdentity::userIdentity) { + this->setIdentity(); } -void ContactsModel::setIdentity(UserIdentity *identity) +void ContactsModel::setIdentity() { - if (identity == m_identity) - return; + Q_ASSERT(m_identity != nullptr); beginResetModel(); - foreach (ContactUser *user, contacts) + foreach (shims::ContactUser *user, contacts) user->disconnect(this); contacts.clear(); @@ -63,15 +65,13 @@ void ContactsModel::setIdentity(UserIdentity *identity) disconnect(&m_identity->contacts, 0, this, 0); } - m_identity = identity; - if (m_identity) { - connect(&identity->contacts, SIGNAL(contactAdded(ContactUser*)), SLOT(contactAdded(ContactUser*))); + connect(&m_identity->contacts, &shims::ContactsManager::contactAdded, this, &ContactsModel::contactAdded); - contacts = identity->contacts.contacts(); + contacts = m_identity->contacts.contacts(); std::sort(contacts.begin(), contacts.end(), contactSort); - foreach (ContactUser *user, contacts) + foreach (shims::ContactUser *user, contacts) connectSignals(user); } @@ -79,7 +79,7 @@ void ContactsModel::setIdentity(UserIdentity *identity) emit identityChanged(); } -QModelIndex ContactsModel::indexOfContact(ContactUser *user) const +QModelIndex ContactsModel::indexOfContact(shims::ContactUser *user) const { int row = contacts.indexOf(user); if (row < 0) @@ -87,16 +87,16 @@ QModelIndex ContactsModel::indexOfContact(ContactUser *user) const return index(row, 0); } -ContactUser *ContactsModel::contact(int row) const +shims::ContactUser *ContactsModel::contact(int row) const { return contacts.value(row); } -void ContactsModel::updateUser(ContactUser *user) +void ContactsModel::updateUser(shims::ContactUser *user) { if (!user) { - user = qobject_cast(sender()); + user = qobject_cast(sender()); if (!user) return; } @@ -108,7 +108,7 @@ void ContactsModel::updateUser(ContactUser *user) return; } - QList sorted = contacts; + QList sorted = contacts; std::sort(sorted.begin(), sorted.end(), contactSort); int newRow = sorted.indexOf(user); @@ -121,20 +121,20 @@ void ContactsModel::updateUser(ContactUser *user) emit dataChanged(index(newRow, 0), index(newRow, 0)); } -void ContactsModel::connectSignals(ContactUser *user) +void ContactsModel::connectSignals(shims::ContactUser *user) { connect(user, SIGNAL(statusChanged()), SLOT(updateUser())); connect(user, SIGNAL(nicknameChanged()), SLOT(updateUser())); - connect(user, SIGNAL(contactDeleted(ContactUser*)), SLOT(contactRemoved(ContactUser*))); + connect(user, &shims::ContactUser::contactDeleted, this, &ContactsModel::contactRemoved);; } -void ContactsModel::contactAdded(ContactUser *user) +void ContactsModel::contactAdded(shims::ContactUser *user) { Q_ASSERT(!indexOfContact(user).isValid()); connectSignals(user); - QList::Iterator lp = qLowerBound(contacts.begin(), contacts.end(), user, contactSort); + QList::Iterator lp = std::lower_bound(contacts.begin(), contacts.end(), user, contactSort); int row = lp - contacts.begin(); beginInsertRows(QModelIndex(), row, row); @@ -142,9 +142,9 @@ void ContactsModel::contactAdded(ContactUser *user) endInsertRows(); } -void ContactsModel::contactRemoved(ContactUser *user) +void ContactsModel::contactRemoved(shims::ContactUser *user) { - if (!user && !(user = qobject_cast(sender()))) + if (!user && !(user = qobject_cast(sender()))) return; int row = contacts.indexOf(user); @@ -176,17 +176,17 @@ QVariant ContactsModel::data(const QModelIndex &index, int role) const if (!index.isValid() || index.row() >= contacts.size()) return QVariant(); - ContactUser *user = contacts[index.row()]; + shims::ContactUser *user = contacts[index.row()]; switch (role) { case Qt::DisplayRole: case Qt::EditRole: - return user->nickname(); + return user->getNickname(); case PointerRole: return QVariant::fromValue(user); case StatusRole: - return user->status(); + return user->getStatus(); } return QVariant(); diff --git a/src/ui/ContactsModel.h b/src/libtego_ui/ui/ContactsModel.h similarity index 75% rename from src/ui/ContactsModel.h rename to src/libtego_ui/ui/ContactsModel.h index 3d60a0d6..007a75f8 100644 --- a/src/ui/ContactsModel.h +++ b/src/libtego_ui/ui/ContactsModel.h @@ -33,19 +33,19 @@ #ifndef CONTACTSMODEL_H #define CONTACTSMODEL_H -#include -#include -class UserIdentity; -class ContactUser; +namespace shims +{ + class UserIdentity; + class ContactUser; +} + class ContactsModel : public QAbstractListModel { Q_OBJECT Q_DISABLE_COPY(ContactsModel) - Q_PROPERTY(UserIdentity* identity READ identity WRITE setIdentity NOTIFY identityChanged) - public: enum { @@ -56,12 +56,9 @@ class ContactsModel : public QAbstractListModel explicit ContactsModel(QObject *parent = 0); - UserIdentity *identity() const { return m_identity; } - void setIdentity(UserIdentity *identity); - - Q_INVOKABLE QModelIndex indexOfContact(ContactUser *user) const; - Q_INVOKABLE int rowOfContact(ContactUser *user) const { return indexOfContact(user).row(); } - Q_INVOKABLE ContactUser *contact(int row) const; + Q_INVOKABLE QModelIndex indexOfContact(shims::ContactUser *user) const; + Q_INVOKABLE int rowOfContact(shims::ContactUser *user) const { return indexOfContact(user).row(); } + Q_INVOKABLE shims::ContactUser *contact(int row) const; virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; virtual QHash roleNames() const; @@ -71,15 +68,17 @@ class ContactsModel : public QAbstractListModel void identityChanged(); private slots: - void updateUser(ContactUser *user = 0); - void contactAdded(ContactUser *user); - void contactRemoved(ContactUser *user); + void updateUser(shims::ContactUser *user = 0); + void contactAdded(shims::ContactUser *user); + void contactRemoved(shims::ContactUser *user); private: - UserIdentity *m_identity; - QList contacts; + void setIdentity(); + + shims::UserIdentity *m_identity; + QList contacts; - void connectSignals(ContactUser *user); + void connectSignals(shims::ContactUser *user); }; #endif // CONTACTSMODEL_H diff --git a/src/ui/LanguagesModel.cpp b/src/libtego_ui/ui/LanguagesModel.cpp similarity index 98% rename from src/ui/LanguagesModel.cpp rename to src/libtego_ui/ui/LanguagesModel.cpp index 3f7eb2d7..98f031bd 100644 --- a/src/ui/LanguagesModel.cpp +++ b/src/libtego_ui/ui/LanguagesModel.cpp @@ -31,9 +31,6 @@ */ #include "LanguagesModel.h" -#include -#include -#include LanguagesModel::LanguagesModel(QObject* parent) : QAbstractListModel(parent) diff --git a/src/ui/LanguagesModel.h b/src/libtego_ui/ui/LanguagesModel.h similarity index 96% rename from src/ui/LanguagesModel.h rename to src/libtego_ui/ui/LanguagesModel.h index 797e8ee8..833849cd 100644 --- a/src/ui/LanguagesModel.h +++ b/src/libtego_ui/ui/LanguagesModel.h @@ -33,8 +33,6 @@ #ifndef LANGUAGESMODEL_H #define LANGUAGESMODEL_H -#include - class LanguagesModel : public QAbstractListModel { Q_OBJECT @@ -60,8 +58,8 @@ class LanguagesModel : public QAbstractListModel QString nativeName; QString localeID; - LanguageEntry(const QString& name, const QString& localeID) - : nativeName(name), localeID(localeID) {} + LanguageEntry(const QString& name, const QString& locale) + : nativeName(name), localeID(locale) {} }; QList languages; diff --git a/src/ui/MainWindow.cpp b/src/libtego_ui/ui/MainWindow.cpp similarity index 64% rename from src/ui/MainWindow.cpp rename to src/libtego_ui/ui/MainWindow.cpp index 356b652f..780ec4cb 100644 --- a/src/ui/MainWindow.cpp +++ b/src/libtego_ui/ui/MainWindow.cpp @@ -30,39 +30,27 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "MainWindow.h" -#include "core/UserIdentity.h" -#include "core/IncomingRequestManager.h" -#include "core/OutgoingContactRequest.h" -#include "core/IdentityManager.h" -#include "core/ContactIDValidator.h" -#include "core/ConversationModel.h" -#include "tor/TorControl.h" -#include "tor/TorManager.h" -#include "tor/TorProcess.h" -#include "ContactsModel.h" -#include "ui/LinkedText.h" +#include "ui/MainWindow.h" +#include "ui/Clipboard.h" +#include "ui/ContactsModel.h" +#include "ui/LanguagesModel.h" + #include "utils/Settings.h" -#include "utils/PendingOperation.h" #include "utils/Useful.h" -#include "ui/LanguagesModel.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -MainWindow *uiMain = 0; -static QObject *linkedtext_singleton(QQmlEngine *, QJSEngine *) -{ - return new LinkedText; -} +// shim replacements +#include "shims/TorControl.h" +#include "shims/TorManager.h" +#include "shims/UserIdentity.h" +#include "shims/ContactsManager.h" +#include "shims/ContactUser.h" +#include "shims/ConversationModel.h" +#include "shims/OutgoingContactRequest.h" +#include "shims/ContactIDValidator.h" +#include "shims/IncomingContactRequest.h" + +MainWindow *uiMain = 0; /* Through the QQmlNetworkAccessManagerFactory below, all network requests * created via QML will be passed to this object; including, for example, @@ -78,17 +66,16 @@ class BlockedNetworkAccessManager : public QNetworkAccessManager BlockedNetworkAccessManager(QObject *parent) : QNetworkAccessManager(parent) { - /* Either of these is sufficient to cause any network request to fail. - * Both of them should be redundant, because createRequest below also + /* This will cause any network request to fail. + * This should be redundant, because createRequest below also * blackholes every request (and crashes for assert builds). */ - setNetworkAccessible(QNetworkAccessManager::NotAccessible); setProxy(QNetworkProxy(QNetworkProxy::Socks5Proxy, QLatin1String("0.0.0.0"), 0)); } protected: virtual QNetworkReply *createRequest(Operation op, const QNetworkRequest &req, QIODevice *outgoingData = 0) { - BUG() << "QML attempted to load a network resource from" << req.url() << " - this is potentially an input sanitization flaw."; + TEGO_BUG() << "QML attempted to load a network resource from" << req.url() << " - this is potentially an input sanitization flaw."; return QNetworkAccessManager::createRequest(op, QNetworkRequest(), outgoingData); } }; @@ -111,22 +98,18 @@ MainWindow::MainWindow(QObject *parent) qml = new QQmlApplicationEngine(this); qml->setNetworkAccessManagerFactory(new NetworkAccessBlockingFactory); - qmlRegisterUncreatableType("im.ricochet", 1, 0, "ContactUser", QString()); - qmlRegisterUncreatableType("im.ricochet", 1, 0, "UserIdentity", QString()); - qmlRegisterUncreatableType("im.ricochet", 1, 0, "ContactsManager", QString()); - qmlRegisterUncreatableType("im.ricochet", 1, 0, "IncomingRequestManager", QString()); - qmlRegisterUncreatableType("im.ricochet", 1, 0, "IncomingContactRequest", QString()); - qmlRegisterUncreatableType("im.ricochet", 1, 0, "OutgoingContactRequest", QString()); - qmlRegisterUncreatableType("im.ricochet", 1, 0, "TorControl", QString()); - qmlRegisterUncreatableType("im.ricochet", 1, 0, "TorProcess", QString()); - qmlRegisterType("im.ricochet", 1, 0, "ConversationModel"); - qmlRegisterType("im.ricochet", 1, 0, "ContactsModel"); - qmlRegisterType("im.ricochet", 1, 0, "ContactIDValidator"); - qmlRegisterType("im.ricochet", 1, 0, "Settings"); - qmlRegisterSingletonType("im.ricochet", 1, 0, "LinkedText", linkedtext_singleton); - qmlRegisterType("im.ricochet", 1, 0, "LanguagesModel"); - - qRegisterMetaType(); + qmlRegisterUncreatableType("im.ricochet", 1, 0, "ContactUser", QString()); + qmlRegisterUncreatableType("im.ricochet", 1, 0, "UserIdentity", QString()); + qmlRegisterUncreatableType("im.ricochet", 1, 0, "ContactsManager", QString()); + qmlRegisterUncreatableType("im.ricochet", 1, 0, "IncomingContactRequest", QString()); + qmlRegisterUncreatableType("im.ricochet", 1, 0, "OutgoingContactRequest", QString()); + qmlRegisterUncreatableType("im.ricochet", 1, 0, "TorControl", QString()); + qmlRegisterType("im.ricochet", 1, 0, "ConversationModel"); + qmlRegisterType<::ContactsModel>("im.ricochet", 1, 0, "ContactsModel"); + qmlRegisterType("im.ricochet", 1, 0, "ContactIDValidator"); + qmlRegisterType<::SettingsObject>("im.ricochet", 1, 0, "Settings"); + qmlRegisterSingletonType<::Clipboard>("im.ricochet", 1, 0, "Clipboard", &Clipboard::singleton_provider); + qmlRegisterType<::LanguagesModel>("im.ricochet", 1, 0, "LanguagesModel"); } MainWindow::~MainWindow() @@ -135,10 +118,9 @@ MainWindow::~MainWindow() bool MainWindow::showUI() { - Q_ASSERT(!identityManager->identities().isEmpty()); - qml->rootContext()->setContextProperty(QLatin1String("userIdentity"), identityManager->identities()[0]); - qml->rootContext()->setContextProperty(QLatin1String("torControl"), torControl); - qml->rootContext()->setContextProperty(QLatin1String("torInstance"), Tor::TorManager::instance()); + qml->rootContext()->setContextProperty(QLatin1String("userIdentity"), shims::UserIdentity::userIdentity); + qml->rootContext()->setContextProperty(QLatin1String("torControl"), shims::TorControl::torControl); + qml->rootContext()->setContextProperty(QLatin1String("torInstance"), shims::TorManager::torManager); qml->rootContext()->setContextProperty(QLatin1String("uiMain"), this); qml->load(QUrl(QLatin1String("qrc:/ui/main.qml"))); @@ -157,7 +139,19 @@ bool MainWindow::showUI() QString MainWindow::version() const { - return qApp->applicationVersion(); + const static auto retval = qApp->applicationVersion(); + return retval; +} + +QString MainWindow::accessibleVersion() const +{ + const static auto retval = [this]() -> QString + { + auto version = this->version(); + return version.replace('.', QString(" %1 ").arg(tr("Version Seperator"))); + }(); + + return retval; } QString MainWindow::aboutText() const @@ -183,13 +177,13 @@ QVariantMap MainWindow::screens() const } /* QMessageBox implementation for Qt <5.2 */ -bool MainWindow::showRemoveContactDialog(ContactUser *user) +bool MainWindow::showRemoveContactDialog(shims::ContactUser *user) { if (!user) return false; QMessageBox::StandardButton btn = QMessageBox::question(0, - tr("Remove %1").arg(user->nickname()), - tr("Do you want to permanently remove %1?").arg(user->nickname())); + tr("Remove %1").arg(user->getNickname()), + tr("Do you want to permanently remove %1?").arg(user->getNickname())); return btn == QMessageBox::Yes; } diff --git a/src/ui/MainWindow.h b/src/libtego_ui/ui/MainWindow.h similarity index 91% rename from src/ui/MainWindow.h rename to src/libtego_ui/ui/MainWindow.h index 575789c9..99987eba 100644 --- a/src/ui/MainWindow.h +++ b/src/libtego_ui/ui/MainWindow.h @@ -33,11 +33,10 @@ #ifndef MAINWINDOW_H #define MAINWINDOW_H -#include -#include - -class ContactUser; -class UserIdentity; +namespace shims +{ + class ContactUser; +} class IncomingContactRequest; class OutgoingContactRequest; class QQmlApplicationEngine; @@ -50,6 +49,7 @@ class MainWindow : public QObject Q_DISABLE_COPY(MainWindow) Q_PROPERTY(QString version READ version CONSTANT) + Q_PROPERTY(QString accessibleVersion READ accessibleVersion CONSTANT) Q_PROPERTY(QString aboutText READ aboutText CONSTANT) Q_PROPERTY(QVariantMap screens READ screens CONSTANT) @@ -61,9 +61,10 @@ class MainWindow : public QObject QString aboutText() const; QString version() const; + QString accessibleVersion() const; QVariantMap screens() const; - Q_INVOKABLE bool showRemoveContactDialog(ContactUser *user); + Q_INVOKABLE bool showRemoveContactDialog(shims::ContactUser *user); // Find parent window of a QQuickItem; exposed as property after Qt 5.4 Q_INVOKABLE QQuickWindow *findParentWindow(QQuickItem *item); diff --git a/src/utils/Settings.cpp b/src/libtego_ui/utils/Settings.cpp similarity index 97% rename from src/utils/Settings.cpp rename to src/libtego_ui/utils/Settings.cpp index b20d330b..3183bb43 100644 --- a/src/utils/Settings.cpp +++ b/src/libtego_ui/utils/Settings.cpp @@ -31,16 +31,6 @@ */ #include "Settings.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include class SettingsFilePrivate : public QObject { @@ -340,9 +330,8 @@ bool SettingsFilePrivate::write(const QStringList &path, const QJsonValue &value originalValue = current; current = value; - ObjectStack::const_iterator it = stack.end(), begin = stack.begin(); - while (it != begin) { - --it; + for (ObjectStack::const_reverse_iterator it = stack.rbegin(); it != stack.rend(); ++it) + { QJsonObject update = it->second; update.insert(currentKey, current); current = update; diff --git a/src/utils/Settings.h b/src/libtego_ui/utils/Settings.h similarity index 98% rename from src/utils/Settings.h rename to src/libtego_ui/utils/Settings.h index 79ad032d..2f2dd86b 100644 --- a/src/utils/Settings.h +++ b/src/libtego_ui/utils/Settings.h @@ -33,13 +33,6 @@ #ifndef SETTINGS_H #define SETTINGS_H -#include -#include -#include -#include -#include -#include - class SettingsObject; class SettingsFilePrivate; class SettingsObjectPrivate; diff --git a/src/utils/Useful.h b/src/libtego_ui/utils/Useful.h similarity index 76% rename from src/utils/Useful.h rename to src/libtego_ui/utils/Useful.h index 6bb6e44c..3e4cce06 100644 --- a/src/utils/Useful.h +++ b/src/libtego_ui/utils/Useful.h @@ -33,24 +33,21 @@ #ifndef UTILS_USEFUL_H #define UTILS_USEFUL_H -#include -#include - /* Print a warning for bug conditions, and assert on a debug build. * * This should be used in place of Q_ASSERT for bug conditions, along * with a proper error case for release-mode builds. For example: * * if (!connection || !user) { - * BUG() << "Request" << request << "should have a connection and user"; + * TEGO_BUG() << "Request" << request << "should have a connection and user"; * return false; * } * - * Do not confuse bugs with actual error cases; BUG() should never be + * Do not confuse bugs with actual error cases; TEGO_BUG() should never be * triggered unless the code or logic is wrong. */ #if !defined(QT_NO_DEBUG) || defined(QT_FORCE_ASSERTS) -# define BUG() Explode(__FILE__,__LINE__), qWarning() << "BUG:" +# define TEGO_BUG() Explode(__FILE__,__LINE__), qWarning() << "BUG:" namespace { class Explode { @@ -64,8 +61,23 @@ class Explode }; } #else -# define BUG() qWarning() << "BUG:" +# define TEGO_BUG() qWarning() << "BUG:" #endif +/* + * helper function for safely casting to QT sizes (generally an int) + * throws if the conversion overflows the target conversion type + */ +template +T safe_cast(F from) +{ + if (from >= std::numeric_limits::max()) + { + TEGO_BUG() << "Invalid safe_cast. Value: " << from + << "; Max value of target type: " << std::numeric_limits::max(); + } + return static_cast(from); +} + #endif diff --git a/src/main.cpp b/src/main.cpp deleted file mode 100644 index 13bfd316..00000000 --- a/src/main.cpp +++ /dev/null @@ -1,364 +0,0 @@ -/* Ricochet - https://ricochet.im/ - * Copyright (C) 2014, John Brooks - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * - * * Neither the names of the copyright owners nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "ui/MainWindow.h" -#include "core/IdentityManager.h" -#include "tor/TorManager.h" -#include "tor/TorControl.h" -#include "utils/CryptoKey.h" -#include "utils/SecureRNG.h" -#include "utils/Settings.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static bool initSettings(SettingsFile *settings, QLockFile **lockFile, QString &errorMessage); -static bool importLegacySettings(SettingsFile *settings, const QString &oldPath); -static void initTranslation(); - -int main(int argc, char *argv[]) -{ - /* Disable rwx memory. - This will also ensure full PAX/Grsecurity protections. */ - qputenv("QV4_FORCE_INTERPRETER", "1"); - qputenv("QT_ENABLE_REGEXP_JIT", "0"); - /* Use QtQuick 2D renderer by default; ignored if not available */ - if (qEnvironmentVariableIsEmpty("QMLSCENE_DEVICE")) - qputenv("QMLSCENE_DEVICE", "softwarecontext"); - - QApplication a(argc, argv); - a.setApplicationVersion(QLatin1String("1.1.4")); - a.setOrganizationName(QStringLiteral("Ricochet")); - -#if !defined(Q_OS_WIN) && !defined(Q_OS_MAC) - a.setWindowIcon(QIcon(QStringLiteral(":/icons/ricochet.svg"))); -#endif - - QScopedPointer settings(new SettingsFile); - SettingsObject::setDefaultFile(settings.data()); - - QString error; - QLockFile *lock = 0; - if (!initSettings(settings.data(), &lock, error)) { - QMessageBox::critical(0, qApp->translate("Main", "Ricochet Error"), error); - return 1; - } - QScopedPointer lockFile(lock); - - initTranslation(); - - /* Initialize OpenSSL's allocator */ -#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) - CRYPTO_malloc_init(); -#else - OPENSSL_malloc_init(); -#endif - - /* Seed the OpenSSL RNG */ - if (!SecureRNG::seed()) - qFatal("Failed to initialize RNG"); - qsrand(SecureRNG::randomInt(UINT_MAX)); - - /* Tor control manager */ - Tor::TorManager *torManager = Tor::TorManager::instance(); - torManager->setDataDirectory(QFileInfo(settings->filePath()).path() + QStringLiteral("/tor/")); - torControl = torManager->control(); - torManager->start(); - - /* Identities */ - identityManager = new IdentityManager; - QScopedPointer scopedIdentityManager(identityManager); - - /* Window */ - QScopedPointer w(new MainWindow); - if (!w->showUI()) - return 1; - - return a.exec(); -} - -static QString userConfigPath() -{ - QString path = QStandardPaths::writableLocation(QStandardPaths::DataLocation); - QString oldPath = path; - oldPath.replace(QStringLiteral("Ricochet"), QStringLiteral("Torsion"), Qt::CaseInsensitive); - if (QFile::exists(oldPath)) - return oldPath; - return path; -} - -#ifdef Q_OS_MAC -static QString appBundlePath() -{ - QString path = QApplication::applicationDirPath(); - int p = path.lastIndexOf(QLatin1String(".app/")); - if (p >= 0) - { - p = path.lastIndexOf(QLatin1Char('/'), p); - path = path.left(p+1); - } - - return path; -} -#endif - -// Writes default settings to settings object. Does not care about any -// preexisting values, therefore this is best used on a fresh object. -static void loadDefaultSettings(SettingsFile *settings) -{ - settings->root()->write("ui.combinedChatWindow", true); -} - -static bool initSettings(SettingsFile *settings, QLockFile **lockFile, QString &errorMessage) -{ - /* If built in portable mode (default), configuration is stored in the 'config' - * directory next to the binary. If not writable, launching fails. - * - * Portable OS X is an exception. In that case, configuration is stored in a - * 'config.ricochet' folder next to the application bundle, unless the application - * path contains "/Applications", in which case non-portable mode is used. - * - * When not in portable mode, a platform-specific per-user config location is used. - * - * This behavior may be overriden by passing a folder path as the first argument. - */ - - QString configPath; - QStringList args = qApp->arguments(); - if (args.size() > 1) { - configPath = args[1]; - } else { -#ifndef RICOCHET_NO_PORTABLE -# ifdef Q_OS_MAC - if (!qApp->applicationDirPath().contains(QStringLiteral("/Applications"))) { - // Try old configuration path first - configPath = appBundlePath() + QStringLiteral("config.torsion"); - if (!QFile::exists(configPath)) - configPath = appBundlePath() + QStringLiteral("config.ricochet"); - } -# else - configPath = qApp->applicationDirPath() + QStringLiteral("/config"); -# endif -#endif - if (configPath.isEmpty()) - configPath = userConfigPath(); - } - - QDir dir(configPath); - if (!dir.exists() && !dir.mkpath(QStringLiteral("."))) { - errorMessage = QStringLiteral("Cannot create directory: %1").arg(dir.path()); - return false; - } - - // Reset to config directory for consistency; avoid depending on this behavior for paths - if (QDir::setCurrent(dir.absolutePath()) && dir.isRelative()) - dir.setPath(QStringLiteral(".")); - - QLockFile *lock = new QLockFile(dir.filePath(QStringLiteral("ricochet.json.lock"))); - *lockFile = lock; - lock->setStaleLockTime(0); - if (!lock->tryLock()) { - if (lock->error() == QLockFile::LockFailedError) { - // This happens if a stale lock file exists and another process uses that PID. - // Try removing the stale file, which will fail if a real process is holding a - // file-level lock. A false error is more problematic than not locking properly - // on corner-case systems. - if (!lock->removeStaleLockFile() || !lock->tryLock()) { - errorMessage = QStringLiteral("Configuration file is already in use"); - return false; - } else - qDebug() << "Removed stale lock file"; - } else { - errorMessage = QStringLiteral("Cannot write configuration file (failed to acquire lock)"); - return false; - } - } - - settings->setFilePath(dir.filePath(QStringLiteral("ricochet.json"))); - if (settings->hasError()) { - errorMessage = settings->errorMessage(); - return false; - } - - if (settings->root()->data().isEmpty()) { - QString filePath = dir.filePath(QStringLiteral("Torsion.ini")); - if (!QFile::exists(filePath)) - filePath = dir.filePath(QStringLiteral("ricochet.ini")); - if (QFile::exists(filePath)) - importLegacySettings(settings, filePath); - } - // if still empty, load defaults here - if (settings->root()->data().isEmpty()) { - loadDefaultSettings(settings); - } - - return true; -} - -static void copyKeys(QSettings &old, SettingsObject *object) -{ - foreach (const QString &key, old.childKeys()) { - QVariant value = old.value(key); - if ((QMetaType::Type)value.type() == QMetaType::QDateTime) - object->write(key, value.toDateTime()); - else if ((QMetaType::Type)value.type() == QMetaType::QByteArray) - object->write(key, Base64Encode(value.toByteArray())); - else - object->write(key, value.toString()); - } -} - -static bool importLegacySettings(SettingsFile *settings, const QString &oldPath) -{ - QSettings old(oldPath, QSettings::IniFormat); - SettingsObject *root = settings->root(); - QVariant value; - - qDebug() << "Importing legacy format settings from" << oldPath; - - if (!(value = old.value(QStringLiteral("tor/controlIp"))).isNull()) - root->write("tor.controlAddress", value.toString()); - if (!(value = old.value(QStringLiteral("tor/controlPort"))).isNull()) - root->write("tor.controlPort", value.toInt()); - if (!(value = old.value(QStringLiteral("tor/authPassword"))).isNull()) - root->write("tor.controlPassword", value.toString()); - if (!(value = old.value(QStringLiteral("tor/socksIp"))).isNull()) - root->write("tor.socksAddress", value.toString()); - if (!(value = old.value(QStringLiteral("tor/socksPort"))).isNull()) - root->write("tor.socksPort", value.toInt()); - if (!(value = old.value(QStringLiteral("tor/executablePath"))).isNull()) - root->write("tor.executablePath", value.toString()); - if (!(value = old.value(QStringLiteral("core/neverPublishService"))).isNull()) - root->write("tor.neverPublishServices", value.toBool()); - if (!(value = old.value(QStringLiteral("identity/0/dataDirectory"))).isNull()) - root->write("identity.dataDirectory", value.toString()); - if (!(value = old.value(QStringLiteral("identity/0/createNewService"))).isNull()) - root->write("identity.initializing", value.toBool()); - if (!(value = old.value(QStringLiteral("core/listenIp"))).isNull()) - root->write("identity.localListenAddress", value.toString()); - if (!(value = old.value(QStringLiteral("core/listenPort"))).isNull()) - root->write("identity.localListenPort", value.toInt()); - - { - old.beginGroup(QStringLiteral("contacts")); - QStringList ids = old.childGroups(); - foreach (const QString &id, ids) { - old.beginGroup(id); - SettingsObject userObject(root, QStringLiteral("contacts.%1").arg(id)); - - copyKeys(old, &userObject); - - if (old.childGroups().contains(QStringLiteral("request"))) { - old.beginGroup(QStringLiteral("request")); - QStringList requestKeys = old.childKeys(); - foreach (const QString &key, requestKeys) - userObject.write(QStringLiteral("request.") + key, old.value(key).toString()); - old.endGroup(); - } - - old.endGroup(); - } - old.endGroup(); - } - - { - old.beginGroup(QStringLiteral("contactRequests")); - QStringList contacts = old.childGroups(); - - foreach (const QString &hostname, contacts) { - old.beginGroup(hostname); - SettingsObject requestObject(root, QStringLiteral("contactRequests.%1").arg(hostname)); - copyKeys(old, &requestObject); - old.endGroup(); - } - - old.endGroup(); - } - - if (!(value = old.value(QStringLiteral("core/hostnameBlacklist"))).isNull()) { - QStringList blacklist = value.toStringList(); - root->write("identity.hostnameBlacklist", QJsonArray::fromStringList(blacklist)); - } - - return true; -} - -static void initTranslation() -{ - QTranslator *translator = new QTranslator; - - bool ok = false; - QString appPath = qApp->applicationDirPath(); - QString resPath = QLatin1String(":/lang/"); - - QLocale locale = QLocale::system(); - if (!qgetenv("RICOCHET_LOCALE").isEmpty()) { - locale = QLocale(QString::fromLatin1(qgetenv("RICOCHET_LOCALE"))); - qDebug() << "Forcing locale" << locale << "from environment" << locale.uiLanguages(); - } - - SettingsObject settings; - QString settingsLanguage(settings.read("ui.language").toString()); - - if (!settingsLanguage.isEmpty()) { - locale = settingsLanguage; - } else { - //write an empty string to get "System default" language selected automatically in preferences - settings.write(QStringLiteral("ui.language"), QString()); - } - - ok = translator->load(locale, QStringLiteral("ricochet"), QStringLiteral("_"), appPath); - if (!ok) - ok = translator->load(locale, QStringLiteral("ricochet"), QStringLiteral("_"), resPath); - - if (ok) { - qApp->installTranslator(translator); - - QTranslator *qtTranslator = new QTranslator; - ok = qtTranslator->load(QStringLiteral("qt_") + locale.name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath)); - if (ok) - qApp->installTranslator(qtTranslator); - else - delete qtTranslator; - } else - delete translator; -} - diff --git a/src/ricochet-refresh/CMakeLists.txt b/src/ricochet-refresh/CMakeLists.txt new file mode 100644 index 00000000..d67d0fa7 --- /dev/null +++ b/src/ricochet-refresh/CMakeLists.txt @@ -0,0 +1,235 @@ +# Ricochet Refresh - https://ricochetrefresh.net/ +# Copyright (C) 2021, Blueprint For Free Speech +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# +# * Neither the names of the copyright owners nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +cmake_minimum_required(VERSION 3.16) + +project(ricochet-refresh LANGUAGES CXX) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if (FORCE_QT5) + find_package( + QT + NAMES + Qt5 + COMPONENTS Core + Gui + Network + Quick + Widgets + LinguistTools + QmlWorkerScript + Multimedia + MultimediaQuick + REQUIRED) +else () + find_package( + QT + NAMES + Qt6 + Qt5 + COMPONENTS Core + Gui + Network + Quick + Widgets + LinguistTools + QmlWorkerScript + Multimedia + MultimediaQuick + REQUIRED) +endif () + +find_package( + Qt${QT_VERSION_MAJOR} + COMPONENTS Core + Gui + Network + Quick + Widgets + LinguistTools + QmlWorkerScript + Multimedia + MultimediaQuick + REQUIRED) + +# Require Qt >5.15 +if (${QT_VERSION_MAJOR} EQUAL 5) + if (${QT_VERSION_MINOR} LESS 15) + message(FATAL_ERROR "Qt >5.15 is required to build this project!") + endif () +endif () + +if (APPLE) + find_package( + Qt${QT_VERSION_MAJOR} + COMPONENTS MacExtras + REQUIRED) +endif () + +include(FindOpenSSL) + +if (APPLE) + add_executable(ricochet-refresh MACOSX_BUNDLE) + set_target_properties(ricochet-refresh PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/resources/darwin/Info.plist.in) + + # Set the icon in info.plist + set(MACOSX_BUNDLE_ICON_FILE "ricochet_refresh.icns") + + # Tell cmake where to look for the actual icons + set(RICOCHET_ICNS_RES ${CMAKE_CURRENT_SOURCE_DIR}/resources/darwin/ricochet_refresh.icns) + set_source_files_properties(${RICOCHET_ICNS_RES} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") + + # And add it to the final app + target_sources(ricochet-refresh PRIVATE ${RICOCHET_ICNS_RES}) +elseif (WIN32) # Despite the naming, this also checks for win64 builds + add_executable(ricochet-refresh WIN32) + + # Add the icon + target_sources(ricochet-refresh PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/resources/windows/resources.rc) +elseif (UNIX) + # Linux or Cygwin (TODO: i'm not too familiar with cygwin, but i think we should build as if we're building for + # linux?) + add_executable(ricochet-refresh) +endif () + +# Add the translations +qt_add_translation(RICOCHET_QM_RES + translation/ricochet_cs.ts + translation/ricochet_ru.ts + translation/ricochet_ja.ts + translation/ricochet_tr.ts + translation/ricochet_zh.ts + translation/ricochet_uk.ts + translation/ricochet_sv.ts + translation/ricochet_sl.ts + translation/ricochet_es.ts + translation/ricochet_pt_BR.ts + translation/ricochet_it.ts + translation/ricochet_bg.ts + translation/ricochet_da.ts + translation/ricochet_et_EE.ts + translation/ricochet_sq.ts + translation/ricochet_he.ts + translation/ricochet_de.ts + translation/ricochet_fi.ts + translation/ricochet_nb.ts + translation/ricochet_it_IT.ts + translation/ricochet_fil_PH.ts + translation/ricochet_fr.ts + translation/ricochet_pt_PT.ts + translation/ricochet_en.ts + translation/ricochet_pl.ts + translation/ricochet_zh_HK.ts + translation/ricochet_nl_NL.ts +) +# The QM files are generated in the build dir, but the qrc file expects the +# QM files to be relative paths. Work around this by copying the qrc file to +# the build dir +configure_file(translation/embedded.qrc ${CMAKE_CURRENT_BINARY_DIR} COPYONLY) + +qt_add_resources( + RICOCHET_QML_RES + ${CMAKE_CURRENT_BINARY_DIR}/embedded.qrc + sounds/sounds.qrc + icons/icons.qrc + ui/qml.qrc) + +target_sources(ricochet-refresh PRIVATE main.cpp ${RICOCHET_QML_RES} ${RICOCHET_QM_RES}) +if (STATIC_QT) + include(qmake_static) + target_generate_static_qml_plugins(ricochet-refresh) + target_generate_static_qt_plugins(ricochet-refresh) +endif () + +target_precompile_headers(ricochet-refresh PRIVATE precomp.hpp) + +include(lto) +include(compiler_opts) +# enables compiler specific warnings/sanitizers if requested +setup_compiler(ricochet-refresh) + +target_compile_features(ricochet-refresh PRIVATE cxx_std_20) + +target_link_libraries(ricochet-refresh PUBLIC tego tego_ui) + +if (NOT USE_SUBMODULE_FMT) + find_package(fmt REQUIRED) +endif () +target_link_libraries(ricochet-refresh PRIVATE fmt::fmt-header-only) +target_link_libraries(ricochet-refresh PRIVATE OpenSSL::Crypto) + +target_link_libraries( + ricochet-refresh + PRIVATE Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Widgets + Qt${QT_VERSION_MAJOR}::Network + Qt${QT_VERSION_MAJOR}::Qml + Qt${QT_VERSION_MAJOR}::Quick + Qt${QT_VERSION_MAJOR}::QmlWorkerScript + Qt${QT_VERSION_MAJOR}::Multimedia + Qt${QT_VERSION_MAJOR}::MultimediaQuick) +if (APPLE) + target_link_libraries(ricochet-refresh PRIVATE Qt${QT_VERSION_MAJOR}::MacExtras) +endif () + +if ("${CMAKE_BUILD_TYPE}" MATCHES "Rel.*" OR "${CMAKE_BUILD_TYPE}" STREQUAL "MinSizeRel") + target_compile_definitions(ricochet-refresh PRIVATE QT_NO_DEBUG_OUTPUT QT_NO_WARNING_OUTPUT) +endif () + +# Linux / Cygwin +if (UNIX) + # Again, not sure if this needs to be UNIX AND NOT WIN32, or if we should + # install to /bin on Cygwin like it does now + install(TARGETS ricochet-refresh DESTINATION usr/bin) +endif () + +# Linux +if (UNIX AND NOT WIN32) + option (RICOCHET_REFRESH_INSTALL_DESKTOP "Install desktop integration files + icons" OFF) + if (RICOCHET_REFRESH_INSTALL_DESKTOP) + install(FILES resources/linux/ricochet-refresh.desktop DESTINATION usr/share/applications) + install(FILES resources/linux/icons/48x48/ricochet-refresh.png DESTINATION usr/share/icons/hicolor/48x48/apps/) + install(FILES resources/linux/icons/scalable/ricochet-refresh.svg DESTINATION usr/share/icons/hicolor/scalable/apps/) + endif () +endif () + +# Move our final binary to a bin dir inside the output dir. This makes it +# easier for integration with ricochet-build +set_target_properties(ricochet-refresh + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/ricochet-refresh/" +) diff --git a/icons/LICENSE b/src/ricochet-refresh/icons/LICENSE similarity index 93% rename from icons/LICENSE rename to src/ricochet-refresh/icons/LICENSE index abc8fb16..d76a4449 100644 --- a/icons/LICENSE +++ b/src/ricochet-refresh/icons/LICENSE @@ -1,6 +1,3 @@ -Ricochet "R" icon -Copyright 2014, Lawrence Eastland (lawrence@eyedesign.com.au) - This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to diff --git a/src/ricochet-refresh/icons/cancel.png b/src/ricochet-refresh/icons/cancel.png new file mode 100644 index 00000000..a6e79b32 Binary files /dev/null and b/src/ricochet-refresh/icons/cancel.png differ diff --git a/src/ricochet-refresh/icons/download.png b/src/ricochet-refresh/icons/download.png new file mode 100644 index 00000000..8eb50454 Binary files /dev/null and b/src/ricochet-refresh/icons/download.png differ diff --git a/src/ricochet-refresh/icons/icons.qrc b/src/ricochet-refresh/icons/icons.qrc new file mode 100644 index 00000000..9b44d6bb --- /dev/null +++ b/src/ricochet-refresh/icons/icons.qrc @@ -0,0 +1,9 @@ + + + ricochet_refresh.png + ricochet_icons.ttf + download.png + cancel.png + + + diff --git a/icons/ricochet_icons.ttf b/src/ricochet-refresh/icons/ricochet_icons.ttf similarity index 100% rename from icons/ricochet_icons.ttf rename to src/ricochet-refresh/icons/ricochet_icons.ttf diff --git a/src/ricochet-refresh/icons/ricochet_refresh.png b/src/ricochet-refresh/icons/ricochet_refresh.png new file mode 100644 index 00000000..1663ddad Binary files /dev/null and b/src/ricochet-refresh/icons/ricochet_refresh.png differ diff --git a/src/ricochet-refresh/main.cpp b/src/ricochet-refresh/main.cpp new file mode 100644 index 00000000..39529c63 --- /dev/null +++ b/src/ricochet-refresh/main.cpp @@ -0,0 +1,447 @@ +/* Ricochet Refresh - https://ricochetrefresh.net/ + * Copyright (C) 2019, Blueprint For Free Speech + * Copyright (C) 2014, John Brooks + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the names of the copyright owners nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ui/MainWindow.h" +#include "utils/Settings.h" + +#include + +// shim replacements +#include "shims/TorControl.h" +#include "shims/TorManager.h" +#include "shims/UserIdentity.h" + +static bool initSettings(SettingsFile *settings, QLockFile **lockFile, QString &errorMessage); +static void initTranslation(); + +int main(int argc, char *argv[]) try +{ + /* Disable rwx memory. + This will also ensure full PAX/Grsecurity protections. */ + qputenv("QV4_FORCE_INTERPRETER", "1"); + qputenv("QT_ENABLE_REGEXP_JIT", "0"); + /* Use QtQuick 2D renderer by default; ignored if not available */ + if (qEnvironmentVariableIsEmpty("QMLSCENE_DEVICE")) + qputenv("QMLSCENE_DEVICE", "softwarecontext"); + + /* https://doc.qt.io/qt-5/highdpi.html#high-dpi-support-in-qt */ + QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + if (!qEnvironmentVariableIsSet("QT_DEVICE_PIXEL_RATIO") + && !qEnvironmentVariableIsSet("QT_AUTO_SCREEN_SCALE_FACTOR") + && !qEnvironmentVariableIsSet("QT_SCALE_FACTOR") + && !qEnvironmentVariableIsSet("QT_SCREEN_SCALE_FACTORS")) { + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + } + + QApplication a(argc, argv); + + tego_context_t* tegoContext = nullptr; + tego_initialize(&tegoContext, tego::throw_on_error()); + + auto tego_cleanup = tego::make_scope_exit([=]() -> void { + tego_uninitialize(tegoContext, tego::throw_on_error()); + }); + + init_libtego_callbacks(tegoContext); + + + a.setApplicationVersion(QLatin1String(TEGO_VERSION_STR)); + +#if !defined(Q_OS_WIN) && !defined(Q_OS_MAC) + a.setWindowIcon(QIcon(QStringLiteral(":/icons/ricochet_refresh.png"))); +#endif + + QScopedPointer settings(new SettingsFile); + SettingsObject::setDefaultFile(settings.data()); + + QString error; + QLockFile *lock = 0; + if (!initSettings(settings.data(), &lock, error)) { + if (error.isEmpty()) { + return 0; + } + QMessageBox::critical(0, qApp->translate("Main", "Ricochet Error"), error); + return 1; + } + QScopedPointer lockFile(lock); + + initTranslation(); + + // init our tor shims + shims::TorControl::torControl = new shims::TorControl(tegoContext); + shims::TorManager::torManager = new shims::TorManager(tegoContext); + + // start Tor + { + std::unique_ptr launchConfig; + tego_tor_launch_config_initialize(tego::out(launchConfig), tego::throw_on_error()); + + auto rawFilePath = (QFileInfo(settings->filePath()).path() + QStringLiteral("/tor/")).toUtf8(); + tego_tor_launch_config_set_data_directory( + launchConfig.get(), + rawFilePath.data(), + static_cast(rawFilePath.size()), + tego::throw_on_error()); + + tego_context_start_tor(tegoContext, launchConfig.get(), tego::throw_on_error()); + } + + /* Identities */ + + // init our shims + shims::UserIdentity::userIdentity = new shims::UserIdentity(tegoContext); + auto contactsManager = shims::UserIdentity::userIdentity->getContacts(); + + auto privateKeyString = SettingsObject("identity").read("privateKey"); + if (privateKeyString.isEmpty()) + { + tego_context_start_service( + tegoContext, + nullptr, + nullptr, + nullptr, + 0, + tego::throw_on_error()); + } + else + { + // construct privatekey from privateKey keyblob + std::unique_ptr privateKey; + auto keyBlob = privateKeyString.toUtf8(); + + tego_ed25519_private_key_from_ed25519_keyblob( + tego::out(privateKey), + keyBlob.data(), + static_cast(keyBlob.size()), + tego::throw_on_error()); + + // load all of our user objects + std::vector userIds; + std::vector userTypes; + auto userIdCleanup = tego::make_scope_exit([&]() -> void + { + std::for_each(userIds.begin(), userIds.end(), &tego_user_id_delete); + }); + + // map strings saved in json with tego types + const static QMap stringToUserType = + { + {QString("allowed"), tego_user_type_allowed}, + {QString("requesting"), tego_user_type_requesting}, + {QString("blocked"), tego_user_type_blocked}, + {QString("pending"), tego_user_type_pending}, + {QString("rejected"), tego_user_type_rejected}, + }; + + auto usersJson = SettingsObject("users").data(); + for(auto it = usersJson.begin(); it != usersJson.end(); ++it) + { + // get the user's service id + const auto serviceIdString = it.key(); + const auto serviceIdRaw = serviceIdString.toUtf8(); + + std::unique_ptr serviceId; + tego_v3_onion_service_id_from_string( + tego::out(serviceId), + serviceIdRaw.data(), + static_cast(serviceIdRaw.size()), + tego::throw_on_error()); + + std::unique_ptr userId; + tego_user_id_from_v3_onion_service_id( + tego::out(userId), + serviceId.get(), + tego::throw_on_error()); + userIds.push_back(userId.release()); + + // load relevant data + const auto& userData = it.value().toObject(); + auto typeString = userData.value("type").toString(); + + Q_ASSERT(stringToUserType.contains(typeString)); + auto type = stringToUserType.value(typeString); + userTypes.push_back(type); + + if (type == tego_user_type_allowed || + type == tego_user_type_pending || + type == tego_user_type_rejected) + { + const auto nickname = userData.value("nickname").toString(); + auto contact = contactsManager->addContact(serviceIdString, nickname); + switch(type) + { + case tego_user_type_allowed: + contact->setStatus(shims::ContactUser::Offline); + break; + case tego_user_type_pending: + contact->setStatus(shims::ContactUser::RequestPending); + break; + case tego_user_type_rejected: + contact->setStatus(shims::ContactUser::RequestRejected); + break; + default: + break; + } + } + } + Q_ASSERT(userIds.size() == userTypes.size()); + const size_t userCount = userIds.size(); + + tego_context_start_service( + tegoContext, + privateKey.get(), + userIds.data(), + userTypes.data(), + userCount, + tego::throw_on_error()); + } + + + /* Window */ + QScopedPointer w(new MainWindow); + if (!w->showUI()) + return 1; + + return a.exec(); +} +catch(std::exception& re) +{ + qDebug() << "Caught Exception: " << re.what(); + return -1; +} + +#ifdef Q_OS_MAC +// returns the directory to place the config.ricochet directory on macOS +// no trailing '/' +static QString appBundlePath() +{ + QString path = QApplication::applicationDirPath(); + // if user left the binaries insidie the app bundle + int p = path.lastIndexOf(QLatin1String(".app/")); + if (p >= 0) + { + // just some binaries floating around somewhere + p = path.lastIndexOf(QLatin1Char('/'), p); + path = path.left(p); + } + + return path; +} +#endif + +// Writes default settings to settings object. Does not care about any +// preexisting values, therefore this is best used on a fresh object. +static void loadDefaultSettings(SettingsFile *settings) +{ + settings->root()->write("ui.combinedChatWindow", true); +} + +static bool initSettings(SettingsFile *settings, QLockFile **lockFile, QString &errorMessage) +{ + /* ricochet-refresh by default loads and saves configuration files from QStandardPaths::AppConfigLocation + * + * Linux: ~/.config/ricochet-refresh + * Windows: C:/Users//AppData/Local/ricochet-refresh + * macOS: ~/Library/Preferences/ + * + * ricochet-refresh can also load configuration files from a custom directory passed in as the first argument + */ + + QString configPath; + QStringList args = qApp->arguments(); + + if (args.size() > 1) { + configPath = args[1]; + } else { + // TODO: remove this profile migration after sufficient time has passed (EOY 2021) + auto legacyConfigPath = []() -> QString { + QString configPath_; +#ifdef Q_OS_MAC + // if the user has installed it to /Applications + if (qApp->applicationDirPath().contains(QStringLiteral("/Applications"))) { + // ~Library/Application Support/Ricochet/Ricochet-Refresh + configPath_ = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/Ricochet/Ricochet-Refresh"); + } else { + configPath_ = appBundlePath() + QStringLiteral("/config.ricochet"); + } +#else + configPath_ = qApp->applicationDirPath() + QStringLiteral("/config"); +#endif + return configPath_; + }(); + auto v3_0_10ConfigPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); + configPath = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); + + logger::println("configPath : {}", configPath); + logger::println("v3.0.10 configPath :{}", v3_0_10ConfigPath); + logger::println("legacyConfigPath : {}", legacyConfigPath); + + // only put up migration UX when + if (// old path differs from new path + configPath != legacyConfigPath && + // the old path exists + QFile::exists(legacyConfigPath) && + // the new path does not exist + !QFile::exists(configPath)) { + + QMessageBox msgBox; + msgBox.setWindowTitle(QStringLiteral("Profile Migration")); + msgBox.setText(QStringLiteral("Ricochet Refresh has detected an existing legacy profile. Do you want to import it?")); + msgBox.setIcon(QMessageBox::Question); + msgBox.setDetailedText( + QStringLiteral( + "Previous versions of Ricochet Refresh stored your profile data in the application's install location. If you import your legacy profile, it will be moved to a new location within your home directory.\n\n" + "Old profile: '%1'\n" + "New profile: '%2'").arg(legacyConfigPath).arg(configPath)); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No | QMessageBox::Abort); + msgBox.setDefaultButton(QMessageBox::Abort); + + switch(static_cast(msgBox.exec())) + { + case QMessageBox::Yes: + // migrate profile + if(!QDir().rename(legacyConfigPath, configPath)) { + errorMessage = QStringLiteral("Unable to migrate profile"); + return false; + } + break; + case QMessageBox::No: + // use old profile path + configPath = legacyConfigPath; + break; + case QMessageBox::Abort: + // exit program + return false; + default: + errorMessage = QStringLiteral("Invalid return value from msgBox.exec()"); + return false; + } + // auto migrate if we have a 3.0.10 config but no 3.0.11+ config + } else if (// 3.0.10 path exists + QFile::exists(v3_0_10ConfigPath) && + // but 3.0.11+ path does not + !QFile::exists(configPath)) { + + /// just automatically move the directory + if(!QDir().rename(v3_0_10ConfigPath, configPath)) { + // on failure use the old path + configPath = v3_0_10ConfigPath; + } + } + } + + QDir dir(configPath); + if (!dir.exists() && !dir.mkpath(QStringLiteral("."))) { + errorMessage = QStringLiteral("Cannot create directory: %1").arg(dir.path()); + return false; + } + + // Reset to config directory for consistency; avoid depending on this behavior for paths + if (QDir::setCurrent(dir.absolutePath()) && dir.isRelative()) + dir.setPath(QStringLiteral(".")); + + QLockFile *lock = new QLockFile(dir.filePath(QStringLiteral("ricochet.json.lock"))); + *lockFile = lock; + lock->setStaleLockTime(0); + if (!lock->tryLock()) { + if (lock->error() == QLockFile::LockFailedError) { + // This happens if a stale lock file exists and another process uses that PID. + // Try removing the stale file, which will fail if a real process is holding a + // file-level lock. A false error is more problematic than not locking properly + // on corner-case systems. + if (!lock->removeStaleLockFile() || !lock->tryLock()) { + errorMessage = QStringLiteral("Configuration file is already in use"); + return false; + } else + qDebug() << "Removed stale lock file"; + } else { + errorMessage = QStringLiteral("Cannot write configuration file (failed to acquire lock)"); + return false; + } + } + + settings->setFilePath(dir.filePath(QStringLiteral("ricochet.json"))); + if (settings->hasError()) { + errorMessage = settings->errorMessage(); + return false; + } + + // if still empty, load defaults here + if (settings->root()->data().isEmpty()) { + loadDefaultSettings(settings); + } + + return true; +} + +static void initTranslation() +{ + QTranslator *translator = new QTranslator; + + bool ok = false; + QString appPath = qApp->applicationDirPath(); + QString resPath = QLatin1String(":/lang/"); + + QLocale locale = QLocale::system(); + if (!qgetenv("RICOCHET_LOCALE").isEmpty()) { + locale = QLocale(QString::fromLatin1(qgetenv("RICOCHET_LOCALE"))); + qDebug() << "Forcing locale" << locale << "from environment" << locale.uiLanguages(); + } + + SettingsObject settings; + QString settingsLanguage(settings.read("ui.language").toString()); + + if (!settingsLanguage.isEmpty()) { + locale = settingsLanguage; + } else { + //write an empty string to get "System default" language selected automatically in preferences + settings.write(QStringLiteral("ui.language"), QString()); + } + + ok = translator->load(locale, QStringLiteral("ricochet"), QStringLiteral("_"), appPath); + if (!ok) + ok = translator->load(locale, QStringLiteral("ricochet"), QStringLiteral("_"), resPath); + + if (ok) { + qApp->installTranslator(translator); + + QTranslator *qtTranslator = new QTranslator; + ok = qtTranslator->load(QStringLiteral("qt_") + locale.name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath)); + if (ok) + qApp->installTranslator(qtTranslator); + else + delete qtTranslator; + } else + delete translator; +} + diff --git a/src/ricochet-refresh/precomp.hpp b/src/ricochet-refresh/precomp.hpp new file mode 100644 index 00000000..c8825015 --- /dev/null +++ b/src/ricochet-refresh/precomp.hpp @@ -0,0 +1,41 @@ +// C headers + +// openssl +#include + +// C++ headers +#ifdef __cplusplus + +// Qt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// tego +#include + +#ifdef TEGO_VERSION +# define TEGO_STR2(X) #X +# define TEGO_STR(X) TEGO_STR2(X) +# define TEGO_VERSION_STR TEGO_STR(TEGO_VERSION) +#else +# define TEGO_VERSION_STR "devbuild" +#endif + +#endif // __cplusplus + + diff --git a/src/Info.plist b/src/ricochet-refresh/resources/darwin/Info.plist.in similarity index 73% rename from src/Info.plist rename to src/ricochet-refresh/resources/darwin/Info.plist.in index 07ca4d7d..10b425d2 100644 --- a/src/Info.plist +++ b/src/ricochet-refresh/resources/darwin/Info.plist.in @@ -5,18 +5,20 @@ NSPrincipalClass NSApplication CFBundleIconFile - Ricochet.icns + ricochet_refresh.icns CFBundlePackageType APPL CFBundleSignature ???? CFBundleExecutable - ricochet + ricochet-refresh CFBundleIdentifier - im.ricochet + net.ricochetrefresh CFBundleName - Ricochet + Ricochet-Refresh NSSupportsAutomaticGraphicsSwitching + NSHighResolutionCapable + diff --git a/src/ricochet-refresh/resources/darwin/ricochet_refresh.icns b/src/ricochet-refresh/resources/darwin/ricochet_refresh.icns new file mode 100644 index 00000000..91393de9 Binary files /dev/null and b/src/ricochet-refresh/resources/darwin/ricochet_refresh.icns differ diff --git a/src/ricochet-refresh/resources/linux/icons/48x48/ricochet-refresh.png b/src/ricochet-refresh/resources/linux/icons/48x48/ricochet-refresh.png new file mode 100644 index 00000000..7a8b4352 Binary files /dev/null and b/src/ricochet-refresh/resources/linux/icons/48x48/ricochet-refresh.png differ diff --git a/src/ricochet-refresh/resources/linux/icons/scalable/ricochet-refresh.svg b/src/ricochet-refresh/resources/linux/icons/scalable/ricochet-refresh.svg new file mode 100644 index 00000000..fc7295ac --- /dev/null +++ b/src/ricochet-refresh/resources/linux/icons/scalable/ricochet-refresh.svg @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/ricochet-refresh/resources/linux/ricochet-refresh.desktop b/src/ricochet-refresh/resources/linux/ricochet-refresh.desktop new file mode 100644 index 00000000..4aa4ccb1 --- /dev/null +++ b/src/ricochet-refresh/resources/linux/ricochet-refresh.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Type=Application +Name=Ricochet Refresh +GenericName=Messenger +Comment=Anonymous instant messaging +Categories=Network;InstantMessaging;Chat;P2P +Icon=ricochet-refresh +Exec=ricochet-refresh diff --git a/src/ricochet-refresh/resources/windows/resources.rc b/src/ricochet-refresh/resources/windows/resources.rc new file mode 100644 index 00000000..630af16f --- /dev/null +++ b/src/ricochet-refresh/resources/windows/resources.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON DISCARDABLE "ricochet_refresh.ico" \ No newline at end of file diff --git a/src/ricochet-refresh/resources/windows/ricochet_refresh.ico b/src/ricochet-refresh/resources/windows/ricochet_refresh.ico new file mode 100644 index 00000000..969d9f67 Binary files /dev/null and b/src/ricochet-refresh/resources/windows/ricochet_refresh.ico differ diff --git a/sounds/LICENSE b/src/ricochet-refresh/sounds/LICENSE similarity index 100% rename from sounds/LICENSE rename to src/ricochet-refresh/sounds/LICENSE diff --git a/sounds/message.wav b/src/ricochet-refresh/sounds/message.wav similarity index 100% rename from sounds/message.wav rename to src/ricochet-refresh/sounds/message.wav diff --git a/sounds/online.wav b/src/ricochet-refresh/sounds/online.wav similarity index 100% rename from sounds/online.wav rename to src/ricochet-refresh/sounds/online.wav diff --git a/sounds/sounds.qrc b/src/ricochet-refresh/sounds/sounds.qrc similarity index 100% rename from sounds/sounds.qrc rename to src/ricochet-refresh/sounds/sounds.qrc diff --git a/translation/embedded.qrc b/src/ricochet-refresh/translation/embedded.qrc similarity index 100% rename from translation/embedded.qrc rename to src/ricochet-refresh/translation/embedded.qrc diff --git a/translation/inno/Albanian.isl b/src/ricochet-refresh/translation/inno/Albanian.isl similarity index 100% rename from translation/inno/Albanian.isl rename to src/ricochet-refresh/translation/inno/Albanian.isl diff --git a/translation/inno/Bulgarian.isl b/src/ricochet-refresh/translation/inno/Bulgarian.isl similarity index 100% rename from translation/inno/Bulgarian.isl rename to src/ricochet-refresh/translation/inno/Bulgarian.isl diff --git a/translation/inno/ChineseSimplified.isl b/src/ricochet-refresh/translation/inno/ChineseSimplified.isl similarity index 100% rename from translation/inno/ChineseSimplified.isl rename to src/ricochet-refresh/translation/inno/ChineseSimplified.isl diff --git a/translation/inno/Estonian.isl b/src/ricochet-refresh/translation/inno/Estonian.isl similarity index 100% rename from translation/inno/Estonian.isl rename to src/ricochet-refresh/translation/inno/Estonian.isl diff --git a/translation/inno/Swedish.isl b/src/ricochet-refresh/translation/inno/Swedish.isl similarity index 100% rename from translation/inno/Swedish.isl rename to src/ricochet-refresh/translation/inno/Swedish.isl diff --git a/translation/installer_bg.isl b/src/ricochet-refresh/translation/installer_bg.isl similarity index 100% rename from translation/installer_bg.isl rename to src/ricochet-refresh/translation/installer_bg.isl diff --git a/translation/installer_cs.isl b/src/ricochet-refresh/translation/installer_cs.isl similarity index 100% rename from translation/installer_cs.isl rename to src/ricochet-refresh/translation/installer_cs.isl diff --git a/translation/installer_da.isl b/src/ricochet-refresh/translation/installer_da.isl similarity index 100% rename from translation/installer_da.isl rename to src/ricochet-refresh/translation/installer_da.isl diff --git a/translation/installer_de.isl b/src/ricochet-refresh/translation/installer_de.isl similarity index 100% rename from translation/installer_de.isl rename to src/ricochet-refresh/translation/installer_de.isl diff --git a/translation/installer_en.isl b/src/ricochet-refresh/translation/installer_en.isl similarity index 100% rename from translation/installer_en.isl rename to src/ricochet-refresh/translation/installer_en.isl diff --git a/translation/installer_es.isl b/src/ricochet-refresh/translation/installer_es.isl similarity index 100% rename from translation/installer_es.isl rename to src/ricochet-refresh/translation/installer_es.isl diff --git a/translation/installer_et_EE.isl b/src/ricochet-refresh/translation/installer_et_EE.isl similarity index 100% rename from translation/installer_et_EE.isl rename to src/ricochet-refresh/translation/installer_et_EE.isl diff --git a/translation/installer_fi.isl b/src/ricochet-refresh/translation/installer_fi.isl similarity index 100% rename from translation/installer_fi.isl rename to src/ricochet-refresh/translation/installer_fi.isl diff --git a/translation/installer_fr.isl b/src/ricochet-refresh/translation/installer_fr.isl similarity index 100% rename from translation/installer_fr.isl rename to src/ricochet-refresh/translation/installer_fr.isl diff --git a/translation/installer_it.isl b/src/ricochet-refresh/translation/installer_it.isl similarity index 100% rename from translation/installer_it.isl rename to src/ricochet-refresh/translation/installer_it.isl diff --git a/translation/installer_it_IT.isl b/src/ricochet-refresh/translation/installer_it_IT.isl similarity index 100% rename from translation/installer_it_IT.isl rename to src/ricochet-refresh/translation/installer_it_IT.isl diff --git a/translation/installer_ja.isl b/src/ricochet-refresh/translation/installer_ja.isl similarity index 100% rename from translation/installer_ja.isl rename to src/ricochet-refresh/translation/installer_ja.isl diff --git a/translation/installer_nb.isl b/src/ricochet-refresh/translation/installer_nb.isl similarity index 100% rename from translation/installer_nb.isl rename to src/ricochet-refresh/translation/installer_nb.isl diff --git a/translation/installer_nl_NL.isl b/src/ricochet-refresh/translation/installer_nl_NL.isl similarity index 100% rename from translation/installer_nl_NL.isl rename to src/ricochet-refresh/translation/installer_nl_NL.isl diff --git a/translation/installer_pl.isl b/src/ricochet-refresh/translation/installer_pl.isl similarity index 100% rename from translation/installer_pl.isl rename to src/ricochet-refresh/translation/installer_pl.isl diff --git a/translation/installer_pt_BR.isl b/src/ricochet-refresh/translation/installer_pt_BR.isl similarity index 100% rename from translation/installer_pt_BR.isl rename to src/ricochet-refresh/translation/installer_pt_BR.isl diff --git a/translation/installer_pt_PT.isl b/src/ricochet-refresh/translation/installer_pt_PT.isl similarity index 100% rename from translation/installer_pt_PT.isl rename to src/ricochet-refresh/translation/installer_pt_PT.isl diff --git a/translation/installer_ru.isl b/src/ricochet-refresh/translation/installer_ru.isl similarity index 100% rename from translation/installer_ru.isl rename to src/ricochet-refresh/translation/installer_ru.isl diff --git a/translation/installer_sl.isl b/src/ricochet-refresh/translation/installer_sl.isl similarity index 100% rename from translation/installer_sl.isl rename to src/ricochet-refresh/translation/installer_sl.isl diff --git a/translation/installer_sq.isl b/src/ricochet-refresh/translation/installer_sq.isl similarity index 100% rename from translation/installer_sq.isl rename to src/ricochet-refresh/translation/installer_sq.isl diff --git a/translation/installer_sv.isl b/src/ricochet-refresh/translation/installer_sv.isl similarity index 100% rename from translation/installer_sv.isl rename to src/ricochet-refresh/translation/installer_sv.isl diff --git a/translation/installer_tr.isl b/src/ricochet-refresh/translation/installer_tr.isl similarity index 100% rename from translation/installer_tr.isl rename to src/ricochet-refresh/translation/installer_tr.isl diff --git a/translation/installer_uk.isl b/src/ricochet-refresh/translation/installer_uk.isl similarity index 100% rename from translation/installer_uk.isl rename to src/ricochet-refresh/translation/installer_uk.isl diff --git a/translation/installer_zh.isl b/src/ricochet-refresh/translation/installer_zh.isl similarity index 100% rename from translation/installer_zh.isl rename to src/ricochet-refresh/translation/installer_zh.isl diff --git a/translation/installer_zh_HK.isl b/src/ricochet-refresh/translation/installer_zh_HK.isl similarity index 100% rename from translation/installer_zh_HK.isl rename to src/ricochet-refresh/translation/installer_zh_HK.isl diff --git a/src/ricochet-refresh/translation/ricochet_bg.ts b/src/ricochet-refresh/translation/ricochet_bg.ts new file mode 100644 index 00000000..c744d9bb --- /dev/null +++ b/src/ricochet-refresh/translation/ricochet_bg.ts @@ -0,0 +1,1257 @@ + + + + + AboutPreferences + + Ricochet %1 + %1 version, e.g. 1.0.0 + Рикошет %1 + + + + Ricochet-Refresh %1 + %1 version, e.g. 1.0.0 + + + + + Ricochet Refresh version %1 + provides a readable interpretation of the Ricochet-Refresh version number for accessibility tech like screen readers + + + + + The license of Ricochet Refresh and its dependencies + + + + + License + + + + + About + За Програмта + + + + About page, contains license and version information + summary of the window's contents for accessibility tech like screen readers + + + + + Current Ricochet-Refresh version + + + + + AddContactDialog + + + Share your Ricochet ID to allow connection requests + tells the user the purpose of their Ricochet ID, which is basically a username + Сподели своят Рикошет Идентификатор, за да позволиш нови покани + + + + Cancel + label for button which dismisses a dialog + Откажи + + + + Closes the contact add window + description for 'Cancel' button accessibility tech like screen readers + + + + + Add + button label to finish adding a contact/friend + Добави + + + + Adds the contact to your contact list + description for 'Add' button for accessibility tech like screen readres + + + + + ChatPage + + + Message area + label for accessibility tech like screen readers + + + + + Write the message to be sent here. Press enter to send + description of the text area used to send messages for accessibility tech like screen readers + + + + + ContactActions + + + Warning + + + + + Could not successfully export conversation + + + + + Open Window + Context menu command to open the chat screen in a separate window + Отвори Прозорец + + + + Details... + Context menu command to open a window showing the selected contact's details + Допълнитлно... + + + + Rename + Context menu command to rename the selected contact + Преименувай + + + + Send File... + Context menu command to initiate a file transfer, opens a system file dialog + + + + + Export Conversation + Context menu command to initiate a chat log export, opens a system file dialog to export to + + + + + Remove + Context menu command to remove a contact from the contact list + Премахни + + + + Contact options + Description of the items in the context menu for accessibility tech like screen readers + + + + + ContactIDField + + + <b>%1</b> is already your contact + Error message showed when user attempts to add a contact already in their contact list + <b>%1</b> вече е твой контакт + + + + This ID is invalid + Error message showed when the id doesn't comply with spec https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt + + + + + You can't add yourself as a contact + Error message showed when user attempts to add themselves as a contact in their contact list + Не може да добавиш себе си като контакт + + + + Enter an ID starting with <b>ricochet:</b> + Error message showed when the provided ricochet id is invalid + Въведи Идентификатор с <b>рикошет:</b> + + + + Copied to clipboard + Message displayed when text is copied to the user's clipboard + Копирано + + + + Copy + Text displayed on a button used to copy somethign to the user's clipboard + Копирай + + + + Copies the ricochet id to the clipboard + Text description of ricochet id copy button for accessibility tech like screen readers + + + + + ContactList + + + Online + Section header in the contact list for users which are online + На линия + + + + Offline + Section header in the contact list for users which are offline + Отписан + + + + Requests + Section header in the contact list for users requesting to be added to the user's contact list + Молби + + + + Rejected + Section header in the contact list for users that have rejected the user's request to be added to their contact list + Отхвърлени + + + + Status for the given contacts + Description of the section header for accessibility tech like screen readers + + + + Outdated + Стара версия + + + + ContactPreferences + + Date added: + Дата на добавяне: + + + Last seen: + Последно видян: + + + Request: + Покана: + + + Pending connection + В очакване на връзка + + + Delivered + Доставено + + + Accepted + Прието + + + Error + Грешка + + + Rejected + Отхвърлено + + + %1 (Connected) + %1 status, e.g. "Accepted" + %1 (Свързани) + + + Response: + Отговор: + + + + Contact list + Description of the list of contacts for accessibility tech like screen readers + + + + + Contact ID for + Description of text box containing a contact's contact id for accessibility tech like screen readers + + + + + + Unknown user + A placeholder name for a contact whose name we do not know + + + + + Rename + Label for button which allows renaming of a contact + Преименувай + + + + Renames this contact + Description of button which renames a contact for accessibility tech like screen readers + + + + + Remove + Label for button which removes a contact from the contact list + Премахни + + + + Removes this contact + Description of button which removes a user from the contact list for accessibility tech like screen readers + + + + + Preferences for contact + Description of the contents of the 'Contacts' window for accessibility tech like screen readers + + + + + Contact Preferences Window + Name of the contact preferences window for accessibility tech like screen readers + + + + + A list of all your contacts, with their ricochet IDs, and options such as renaming and removing + Description of what user can do in the contact preferences window for accessibility tech like screen readers + + + + + ContactRequestDialog + + + Someone new is asking to connect to you + Descriptive text that is displayed in a popup window when a user tries to add you as a contact + Нов човек те моли да се свържете + + + + Reject + Label for button which rejects a contact request when pressed + Отхвърли + + + + Rejects the incoming contact request + Description of what 'Reject' button does for accessibility tech like screen readers + + + + + Accept + Label for button which accepts a contact request when pressed + Приеми + + + + Accepts the incoming contact request + Description of what 'Accept' button does for accessibility tech like screen readers + + + + + ContactRequestFields + + + ID: + Label for the contact id text box in the 'add new contact' window + Индентификатор: + + + + Name: + Label for the contact nickname text box in the 'add new contact' window + Име: + + + + Field for the contact's nickname + Description of textbox for setting a contact's nickname for accessibility tech like screen readers + + + + + Message: + Label for the contact greeting message text box in the 'add new contact' window + Съобщение: + + + + Field for the contact's greeting message + Description of textbox for setting a new contact's initial greeting message for accessibility tech like screen readers + + + + + GeneralPreferences + + + Use a single window for conversations + Text description of an option to use one single program window for the contact list and the chats + Използвай само един прозорец за всички разговори + + + Open links in default browser without prompting + Text description of an option to open URLs in default web browser when clicked + Отваряй връзките в браузъра по подразбиране без да искаш разрешение + + + + Play audio notifications + Text description of an option to play audio notifications when contacts log in, log out, and send messages + Използвай звукови известия + + + + + Volume + Label for a slider used to adjust audio notification volume +---------- +Name of the slider used to adjust audio notification volume for accessibility tech like screen readers + Звук + + + + + Language + Label for combobox where users can specify the UI language +---------- +Name of the combobox used to select UI langauge for accessibility tech like screen readers + Език + + + + Restart Ricochet to apply changes + Рестартирай Richchet, за да приложиш промените + + + + What language ricochet will use + Description of what the language combox is for for accessibility tech like screen readers + + + + + LanguagePreferences + + Select Language + Избери език + + + Restart Ricochet to apply changes + Рестартирай Richchet, за да приложиш промените + + + + LanguagesModel + + + System default + Настройки по подразбиране + + + + Main + + + Ricochet Error + Рикошет Грешка + + + + MainToolBar + + + + Add Contact + Tooltip text for the button that launches the dialog box for adding a new contact +---------- +Name of the button for adding a new contact for accessibility tech like screen readers + Добави Контакт + + + + + Preferences + Tooltip text for the button that launches program preferences window +---------- +Name of the button for launching the preferences window for accessibility tech like screen readres + Настройки + + + + Click to add contacts + Tooltip that displays on first launch indicating how to add a new contact + Кликни, за да добавиш контакт + + + + Shows the add contact dialogue + Description of the 'Add Contact' button for accessibility tech like screen readers + + + + + Shows the preferences dialogue + Description of the 'Preferences' button for accessibility tech like screen readers + + + + + MainWindow + + + Version Seperator + + + + + Remove %1 + Премахни %1 + + + + Do you want to permanently remove %1? + Наистина ли искаш да премахнеш %1 завинаги? + + + + Main Toolbar + Name of the main toolbar for accessibility tech like screen readers + + + + + Toolbar with connection status, add contact button, and preferences button + Description of the main toolbar for accessibility tech like screen readers + + + + + Contact pane + Name of the pane holding the user's contacts for accessibility tech like screen readers + + + + + MessageDelegate + + + %1 is offline + %1 nickname + %1 е отписан + + + + File transfer file name + Description of the text displaying the filename of a file transfer, used by accessibility tech like screen readres + + + + + File transfer progress + Description of progress bar displaying the file transfer progress, used by accessibility tech like screen readers + + + + + File transfer status + Description of label displaying the current status of a file transfer, used by accessibility tech like screen readers + + + + + Download + Label for file transfer 'Download' button for accessibility tech like screen readers + + + + + Download file + Description of what the file transfer 'Download' button does for accessibility tech like screen readers + + + + + Cancel or reject + Label for file transfer 'Cancel' button for accessibility tech like screen readers + + + + + Cancels or rejects a file transfer + Description of what the file transfer 'Cancel' button does for accessibility tech like screen readers + + + + Copy ID + Text for context menu command to copy a ricochet contact id to clipboard + Копирай Идентификатора + + + Copy Link + Text for context menu command to copy a url to the clipboard + Копирай Връзката + + + Open with Browser + Text for context menu command to open a url in a web browser + Отвори в Браузъра + + + Add as Contact + Доабви като Контакт + + + + Copy Message + Text for context menu command to copy an entire message to clipboard + Копирай съобщение + + + + Copy Selection + Text for context menu command to copy selected text to clipboard + Копирай селекцията + + + + MessageDialogWrapper + + + Remove %1 + A command to remove a contact from the contact list, %1 is the contact's nickname + Премахни %1 + + + + Do you want to permanently remove %1? + Confirmation of contact removal, %1 is the contact's nickname + Наистина ли искаш да премахнеш %1 завинаги? + + + + This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. + Message describing what will happen when a contact is removed from you contact list + Този контакт повече няма да може да ти пише, и ще бъде уведомен за премахването. Той може да реши да ти прати нова покана за връзка. + + + + NetworkSetupWizard + + + This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. + A label with directions for when to use the 'Connect' button + Интернет връзката на този компютър не е възпрепятствана. Искам да се свържа директно с Тор мрежата. + + + + Connect + Label for button to connect to the Tor network + Свържи + + + + This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. + A label with directions for when to use the 'Configure' button + Интернет връзката на този компютър е цензурирана, филтрирана или минава през прокси. Трябва да променя настройките на мрежата . + + + + Configure + Label for button to configure the Tor daemon beore connecting to the Tor network + Настрой + + + + OfflineStateItem + + + Configure + Button label + Настрой + + + + Details + Button label + Допълнително + + + + Connection failed + Label displayed when connecting to the Tor network fails + Неуспешна връзка + + + + Connecting… + Label displayed when in process of connecting, … is ellipsis + Свързване... + + + + OpenBrowserDialog + + <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. + Label displayed when user clicks on a link + <b>Внимание!</b> Отварянето на връзки с браузъра по подразбиране може да прекрати сигурната и анонимността ти.<br><br>Вместо това може да го <a href='.'>копираш</a>. + + + Don't ask again for links from %1 + Не искай повече връзки от %1 + + + Don't ask again for any links (not recommended!) + Checkbox option text for when user clicks on a link + Не искай повече никакви връзки (не се препоръчва!) + + + Open Browser + Label on button to open link in a web browser + Отвори Браузъра + + + Cancel + Label on cancel button + Откажи + + + + PreferencesDialog + + + Ricochet Preferences + Настройки на Рикошет + + + + Menu Tabs + Name of the tab bar for accessibility tech like screen readers + + + + + General + Title of the general settings tab + Главни + + + Language + Език + + + + Contacts + Title of the contacts list tab + Контакти + + + + Tor + Title of the tor tab, contains tor settings and logs + Тор + + + + About + Title of the about tab, contains license information and ricochet version + За Програмта + + + + QCocoaMenuItem + + + Preference + Настройки + + + + StartupStatusPage + + + The Tor process was not started successfully. This is most likely an installation or system error. + Error status string displayed when tor daemon does not launch successfully + Неуспешно стартиране на Тор процесът. Това най-вероятно се дължи на инсталационна или системна грешка. + + + + Quit + Button title to quit/terminate the program + Изход + + + + TorBootstrapStatus + + + Connecting to the Tor network… + Text status displayed when tor daemon is connecting, … is ellipsis + Свързване с Тор мрежата... + + + + Connection status + Description of the connection status text, used by accessibility tech like screen readers + + + + + Connection progress + Description of the bootstrap progress bar, used by accessibility tech like screen readers + + + + + + Back + Label for button which moves us back to previous screen + Назад + + + + Hide Details + Label for button which hides the tor logs + + + + + Show Details + Label for button which shows the tor logs + + + + + Hides the tor progress log + Description of hide details button, used by accessibility tech like screen readers + + + + + Shows the tor progress log + Description of show details button, used by accessibility tech like screen readers + + + + Hide details + Скрий допълнителната информация + + + Show details + Покажи допълнителна информация + + + + Done + Label for button which closes the tor connection window + Готово + + + + TorConfigurationPage + + + Does this computer need a proxy to access the internet? + Този компютър нуждае ли се от прокси, за да се свърже с интернет? + + + + Proxy type: + Вид Прокси: + + + + + None + Никакво + + + + If you need a proxy to access the internet, select one from this list. + Description used by accessibility tech, such as screen readers + + + + + Address: + Label indicating the textbox to place a proxy IP or URL + Адрес: + + + + IP address or hostname + Placeholder text of text box expecting an IP or URL for proxy + IP адрес или hostname + + + + Enter the IP address or hostname of the proxy you wish to connect to + Description of what to enter into the IP textbox, used by accessibility tech such as screen readers + + + + + Port: + Label indicating the textbox to place a proxy port + Порт: + + + + Port + Name of the port label, used by accessibility tech such as screen readers + + + + + Enter the port of the proxy you wish to connect to + Description of what to enter into the Port textbox, used by accessibility tech such as screen readers + + + + + Username: + Label indicating the textbox to place the proxy username + Потребителско Име: + + + + + Optional + Textbox placeholder text indicating the field is not required + Незадължително + + + + Username + Name of the username label, used by accessibility tech such as screen readers + + + + + If required, enter the username for the proxy you wish to connect to + Description to enter into the Username textbox, used by accessibility tech such as screen readers + + + + + Password: + Label indicating the textbox to place the proxy password + Парола: + + + + Password + Name of the password label, used by accessibility tech such as screen readers + + + + + If required, enter the password for the proxy you wish to connect to + Description to enter into the Password textbox, used by accessibility tech such as screen readers + + + + + Does this computer's Internet connection go through a firewall that only allows connections to certain ports? + Description for the purpose of the Allowed Ports textbox + Минава ли интернет връзката на този компютър през firewall програма, която позволява връзка само през определени портове? + + + + Allowed ports: + Label indicating the textbox to place the allowed ports + Разрешени портове: + + + + Example: 80,443 + Textbox showing an example entry for the firewall allowed ports entry + Пример: 80,443 + + + + Allowed ports + Name of the allowed ports label, used by accessibility tech such as screen readers + + + + + If this computer's Internet connection is censored, you will need to obtain and use bridge relays. + Ако интернет връзката на този компютър е цензурирана, ще се наложи да намериш и използваш bridge relays. + + + + + Enter one or more bridge relays (one per line): + Въведи едно или повече bridge relays (по едно на ред): + + + + Back + Button label for going back to previous screen + Назад + + + + Connect + Button label for connecting to tor + Свържи + + + + TorLogDisplay + + + Tor log + Name of the text field containg the tor logs, used by accessibility tech such as screen readers + + + + + TorPreferences + + + Running: + Display label that beside it indicates whether tor is running + Изпълнява: + + + + Running + + + + + Whether tor is running + Description of the value of this label, used by acccessibility tech like screen readers + + + + + + Yes + Да + + + + + No + Не + + + External + Външен + + + + Control connected: + Display label that beside it indicates whether ricochet is connected to tor's control port + Control connected: + + + + Control connected + + + + + Whether tor control connected + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Circuits established: + Circuits established: + + + + Circuits established + + + + + Whether circuits established + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Hidden service: + Hidden service: + + + + Hidden service + + + + + Online + На Линия + + + + Offline + Отписан + + + + Whether a hidden service is up or not + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Version: + Версия: + + + + Version + + + + + Error: <b>%1</b> + %1 is error message + Грешка: <b>%1</b> + + + + Configure + Настрой + + + + TorStateWidget + + + + Connection failed + Неуспешна връзка + + + + + Connecting… + … is ellipsis + Свързване... + + + + Connecting… (%1%) + %1 is progress percentage, e.g. 100 + Свързване... (%1%) + + + + Online + На линия + + + + Connected + Свързан + + + + Tor status + + + + + shims::ConversationModel + + + Pending + + + + + Accepted + Прието + + + + Rejected + + + + + Cancelled + + + + + Complete + + + + + Unkown Failure + + + + + Bad File Hash + + + + + Network Error + + + + + File System Error + + + + + Invalid + + + + + + Open File + + + + + + + + me + + + + + Save File + + + + diff --git a/src/ricochet-refresh/translation/ricochet_cs.ts b/src/ricochet-refresh/translation/ricochet_cs.ts new file mode 100644 index 00000000..f658a6dd --- /dev/null +++ b/src/ricochet-refresh/translation/ricochet_cs.ts @@ -0,0 +1,1257 @@ + + + + + AboutPreferences + + Ricochet %1 + %1 version, e.g. 1.0.0 + Ricochet %1 + + + + Ricochet-Refresh %1 + %1 version, e.g. 1.0.0 + + + + + Ricochet Refresh version %1 + provides a readable interpretation of the Ricochet-Refresh version number for accessibility tech like screen readers + + + + + The license of Ricochet Refresh and its dependencies + + + + + License + + + + + About + O programu + + + + About page, contains license and version information + summary of the window's contents for accessibility tech like screen readers + + + + + Current Ricochet-Refresh version + + + + + AddContactDialog + + + Share your Ricochet ID to allow connection requests + tells the user the purpose of their Ricochet ID, which is basically a username + Sdílejte vaše Richochet ID pro umožnění zádostí připojení + + + + Cancel + label for button which dismisses a dialog + Zrušit + + + + Closes the contact add window + description for 'Cancel' button accessibility tech like screen readers + + + + + Add + button label to finish adding a contact/friend + Přidat + + + + Adds the contact to your contact list + description for 'Add' button for accessibility tech like screen readres + + + + + ChatPage + + + Message area + label for accessibility tech like screen readers + + + + + Write the message to be sent here. Press enter to send + description of the text area used to send messages for accessibility tech like screen readers + + + + + ContactActions + + + Warning + + + + + Could not successfully export conversation + + + + + Open Window + Context menu command to open the chat screen in a separate window + Otevřít okno + + + + Details... + Context menu command to open a window showing the selected contact's details + Podrobnosti... + + + + Rename + Context menu command to rename the selected contact + Přejmenovat + + + + Send File... + Context menu command to initiate a file transfer, opens a system file dialog + + + + + Export Conversation + Context menu command to initiate a chat log export, opens a system file dialog to export to + + + + + Remove + Context menu command to remove a contact from the contact list + Odstranit + + + + Contact options + Description of the items in the context menu for accessibility tech like screen readers + + + + + ContactIDField + + + <b>%1</b> is already your contact + Error message showed when user attempts to add a contact already in their contact list + <b>%1</b> již je v seznamu kontaktů + + + + This ID is invalid + Error message showed when the id doesn't comply with spec https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt + + + + + You can't add yourself as a contact + Error message showed when user attempts to add themselves as a contact in their contact list + Nelze přidat vlastní adresu na seznam kontaktů + + + + Enter an ID starting with <b>ricochet:</b> + Error message showed when the provided ricochet id is invalid + Zadejte ID začínající slovem <b>ricochet:</b> + + + + Copied to clipboard + Message displayed when text is copied to the user's clipboard + Zkopírováno do schránky + + + + Copy + Text displayed on a button used to copy somethign to the user's clipboard + Zkopírovat + + + + Copies the ricochet id to the clipboard + Text description of ricochet id copy button for accessibility tech like screen readers + + + + + ContactList + + + Online + Section header in the contact list for users which are online + Online + + + + Offline + Section header in the contact list for users which are offline + Offline + + + + Requests + Section header in the contact list for users requesting to be added to the user's contact list + Žádosti + + + + Rejected + Section header in the contact list for users that have rejected the user's request to be added to their contact list + Zamítnuto + + + + Status for the given contacts + Description of the section header for accessibility tech like screen readers + + + + Outdated + Zastaralé + + + + ContactPreferences + + Date added: + Přidáno dne: + + + Last seen: + Naposledy online: + + + Request: + Žádost: + + + Pending connection + Čeká se na připojení + + + Delivered + Doručeno + + + Accepted + Přijato + + + Error + Chyba + + + Rejected + Zamítnuto + + + %1 (Connected) + %1 status, e.g. "Accepted" + %1 (Připojeno) + + + Response: + Odpověď: + + + + Contact list + Description of the list of contacts for accessibility tech like screen readers + + + + + Contact ID for + Description of text box containing a contact's contact id for accessibility tech like screen readers + + + + + + Unknown user + A placeholder name for a contact whose name we do not know + + + + + Rename + Label for button which allows renaming of a contact + Přejmenovat + + + + Renames this contact + Description of button which renames a contact for accessibility tech like screen readers + + + + + Remove + Label for button which removes a contact from the contact list + Odstranit + + + + Removes this contact + Description of button which removes a user from the contact list for accessibility tech like screen readers + + + + + Preferences for contact + Description of the contents of the 'Contacts' window for accessibility tech like screen readers + + + + + Contact Preferences Window + Name of the contact preferences window for accessibility tech like screen readers + + + + + A list of all your contacts, with their ricochet IDs, and options such as renaming and removing + Description of what user can do in the contact preferences window for accessibility tech like screen readers + + + + + ContactRequestDialog + + + Someone new is asking to connect to you + Descriptive text that is displayed in a popup window when a user tries to add you as a contact + Někdo nový se s vámi chce spojit + + + + Reject + Label for button which rejects a contact request when pressed + Zamítnout + + + + Rejects the incoming contact request + Description of what 'Reject' button does for accessibility tech like screen readers + + + + + Accept + Label for button which accepts a contact request when pressed + Přijmout + + + + Accepts the incoming contact request + Description of what 'Accept' button does for accessibility tech like screen readers + + + + + ContactRequestFields + + + ID: + Label for the contact id text box in the 'add new contact' window + ID: + + + + Name: + Label for the contact nickname text box in the 'add new contact' window + Jméno: + + + + Field for the contact's nickname + Description of textbox for setting a contact's nickname for accessibility tech like screen readers + + + + + Message: + Label for the contact greeting message text box in the 'add new contact' window + Zpráva: + + + + Field for the contact's greeting message + Description of textbox for setting a new contact's initial greeting message for accessibility tech like screen readers + + + + + GeneralPreferences + + + Use a single window for conversations + Text description of an option to use one single program window for the contact list and the chats + Používat totéž okno pro všechny konverzace + + + Open links in default browser without prompting + Text description of an option to open URLs in default web browser when clicked + Otevírat odkazy v defaultním prohlížeči bez dotazování + + + + Play audio notifications + Text description of an option to play audio notifications when contacts log in, log out, and send messages + Povolit zvukové upozornění + + + + + Volume + Label for a slider used to adjust audio notification volume +---------- +Name of the slider used to adjust audio notification volume for accessibility tech like screen readers + Hlasitost + + + + + Language + Label for combobox where users can specify the UI language +---------- +Name of the combobox used to select UI langauge for accessibility tech like screen readers + Jazyk + + + + Restart Ricochet to apply changes + Restartovat Ricochet aby se změny projevily + + + + What language ricochet will use + Description of what the language combox is for for accessibility tech like screen readers + + + + + LanguagePreferences + + Select Language + Vybrat jazyk + + + Restart Ricochet to apply changes + Restartovat Ricochet aby se změny projevily + + + + LanguagesModel + + + System default + Systémový + + + + Main + + + Ricochet Error + Chyba programu Ricochet + + + + MainToolBar + + + + Add Contact + Tooltip text for the button that launches the dialog box for adding a new contact +---------- +Name of the button for adding a new contact for accessibility tech like screen readers + Přidat kontakt + + + + + Preferences + Tooltip text for the button that launches program preferences window +---------- +Name of the button for launching the preferences window for accessibility tech like screen readres + Nastavení + + + + Click to add contacts + Tooltip that displays on first launch indicating how to add a new contact + Klikněte pro přidání kontaktů + + + + Shows the add contact dialogue + Description of the 'Add Contact' button for accessibility tech like screen readers + + + + + Shows the preferences dialogue + Description of the 'Preferences' button for accessibility tech like screen readers + + + + + MainWindow + + + Version Seperator + + + + + Remove %1 + Odstranit %1 + + + + Do you want to permanently remove %1? + Chcete trvale odstranit kontakt %1? + + + + Main Toolbar + Name of the main toolbar for accessibility tech like screen readers + + + + + Toolbar with connection status, add contact button, and preferences button + Description of the main toolbar for accessibility tech like screen readers + + + + + Contact pane + Name of the pane holding the user's contacts for accessibility tech like screen readers + + + + + MessageDelegate + + + %1 is offline + %1 nickname + %1 je offline + + + + File transfer file name + Description of the text displaying the filename of a file transfer, used by accessibility tech like screen readres + + + + + File transfer progress + Description of progress bar displaying the file transfer progress, used by accessibility tech like screen readers + + + + + File transfer status + Description of label displaying the current status of a file transfer, used by accessibility tech like screen readers + + + + + Download + Label for file transfer 'Download' button for accessibility tech like screen readers + + + + + Download file + Description of what the file transfer 'Download' button does for accessibility tech like screen readers + + + + + Cancel or reject + Label for file transfer 'Cancel' button for accessibility tech like screen readers + + + + + Cancels or rejects a file transfer + Description of what the file transfer 'Cancel' button does for accessibility tech like screen readers + + + + Copy ID + Text for context menu command to copy a ricochet contact id to clipboard + Kopírovat ID + + + Copy Link + Text for context menu command to copy a url to the clipboard + Kopírovat odkaz + + + Open with Browser + Text for context menu command to open a url in a web browser + Otevřít v prohlížeči + + + Add as Contact + Přidat jako kontakt + + + + Copy Message + Text for context menu command to copy an entire message to clipboard + Kopírovat zprávu + + + + Copy Selection + Text for context menu command to copy selected text to clipboard + Kopírovat výběr + + + + MessageDialogWrapper + + + Remove %1 + A command to remove a contact from the contact list, %1 is the contact's nickname + Odstranit %1 + + + + Do you want to permanently remove %1? + Confirmation of contact removal, %1 is the contact's nickname + Chcete trvale odstranit kontakt %1? + + + + This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. + Message describing what will happen when a contact is removed from you contact list + Tento kontakt vám nebude moci posílat zprávy a bude informován o odstranění ze seznamu kontaktů. Může však zaslat novou žádost o spojení. + + + + NetworkSetupWizard + + + This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. + A label with directions for when to use the 'Connect' button + Tento počítač se připojuje na internet bez jakýchkoli překážek. Chci se připojit přímo na síť Tor. + + + + Connect + Label for button to connect to the Tor network + Připojit + + + + This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. + A label with directions for when to use the 'Configure' button + Připojení tohoto počítače na internet je cenzurováno, filtrováno nebo vedeno přes proxy. Potřebuji upravit nastavení sítě. + + + + Configure + Label for button to configure the Tor daemon beore connecting to the Tor network + Nastavit + + + + OfflineStateItem + + + Configure + Button label + Nastavit + + + + Details + Button label + Podrobnosti + + + + Connection failed + Label displayed when connecting to the Tor network fails + Připojení selhalo + + + + Connecting… + Label displayed when in process of connecting, … is ellipsis + Připojuje se... + + + + OpenBrowserDialog + + <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. + Label displayed when user clicks on a link + <b>-Upozornění!</b> Otvírání odkazů v defaultním prohlížeči naruší vaši bezpečnost a anonymitu.<br><br>Můžete místo toho odkaz <a href='.'>zkopírovat do schránky</a>. + + + Don't ask again for links from %1 + Znovu nepožadovat odkazy od kontaktu %1 + + + Don't ask again for any links (not recommended!) + Checkbox option text for when user clicks on a link + Znovu nepožadovat žádné odkazy (nedoporučuje se!) + + + Open Browser + Label on button to open link in a web browser + Otevřít prohlížeč + + + Cancel + Label on cancel button + Zrušit + + + + PreferencesDialog + + + Ricochet Preferences + Nastavení programu Ricochet + + + + Menu Tabs + Name of the tab bar for accessibility tech like screen readers + + + + + General + Title of the general settings tab + Obecné + + + Language + Jazyk + + + + Contacts + Title of the contacts list tab + Kontakty + + + + Tor + Title of the tor tab, contains tor settings and logs + Tor + + + + About + Title of the about tab, contains license information and ricochet version + O programu + + + + QCocoaMenuItem + + + Preference + Nastavení + + + + StartupStatusPage + + + The Tor process was not started successfully. This is most likely an installation or system error. + Error status string displayed when tor daemon does not launch successfully + Proces programu Tor nebyl úspěšně spuštěn. Pravděpodobně se jedná o chybu instalace či systému. + + + + Quit + Button title to quit/terminate the program + Ukončit + + + + TorBootstrapStatus + + + Connecting to the Tor network… + Text status displayed when tor daemon is connecting, … is ellipsis + Připojuje se k síti Tor… + + + + Connection status + Description of the connection status text, used by accessibility tech like screen readers + + + + + Connection progress + Description of the bootstrap progress bar, used by accessibility tech like screen readers + + + + + + Back + Label for button which moves us back to previous screen + Zpět + + + + Hide Details + Label for button which hides the tor logs + + + + + Show Details + Label for button which shows the tor logs + + + + + Hides the tor progress log + Description of hide details button, used by accessibility tech like screen readers + + + + + Shows the tor progress log + Description of show details button, used by accessibility tech like screen readers + + + + Hide details + Skrýt podrobnosti + + + Show details + Zobrazit podrobnosti + + + + Done + Label for button which closes the tor connection window + Hotovo + + + + TorConfigurationPage + + + Does this computer need a proxy to access the internet? + Potřebuje tento počítač pro přístup na internet proxy? + + + + Proxy type: + Typ proxy: + + + + + None + Žádný + + + + If you need a proxy to access the internet, select one from this list. + Description used by accessibility tech, such as screen readers + + + + + Address: + Label indicating the textbox to place a proxy IP or URL + Adresa: + + + + IP address or hostname + Placeholder text of text box expecting an IP or URL for proxy + IP adresa nebo hostname + + + + Enter the IP address or hostname of the proxy you wish to connect to + Description of what to enter into the IP textbox, used by accessibility tech such as screen readers + + + + + Port: + Label indicating the textbox to place a proxy port + Port: + + + + Port + Name of the port label, used by accessibility tech such as screen readers + + + + + Enter the port of the proxy you wish to connect to + Description of what to enter into the Port textbox, used by accessibility tech such as screen readers + + + + + Username: + Label indicating the textbox to place the proxy username + Username: + + + + + Optional + Textbox placeholder text indicating the field is not required + Volitelné + + + + Username + Name of the username label, used by accessibility tech such as screen readers + + + + + If required, enter the username for the proxy you wish to connect to + Description to enter into the Username textbox, used by accessibility tech such as screen readers + + + + + Password: + Label indicating the textbox to place the proxy password + Heslo: + + + + Password + Name of the password label, used by accessibility tech such as screen readers + + + + + If required, enter the password for the proxy you wish to connect to + Description to enter into the Password textbox, used by accessibility tech such as screen readers + + + + + Does this computer's Internet connection go through a firewall that only allows connections to certain ports? + Description for the purpose of the Allowed Ports textbox + Prochází připojení tohoto počítače na internet přes firewall, jenž umožňuje připojení pouze na určité porty? + + + + Allowed ports: + Label indicating the textbox to place the allowed ports + Povolené porty: + + + + Example: 80,443 + Textbox showing an example entry for the firewall allowed ports entry + Příklad: 80,443 + + + + Allowed ports + Name of the allowed ports label, used by accessibility tech such as screen readers + + + + + If this computer's Internet connection is censored, you will need to obtain and use bridge relays. + Je-li připojení tohoto počítače na internet cenzurováno, potřebujete získat a používat bridge relays. + + + + + Enter one or more bridge relays (one per line): + Zadejte jeden či více bridge relays (na jednotlivé řádky): + + + + Back + Button label for going back to previous screen + Zpět + + + + Connect + Button label for connecting to tor + Připojit + + + + TorLogDisplay + + + Tor log + Name of the text field containg the tor logs, used by accessibility tech such as screen readers + + + + + TorPreferences + + + Running: + Display label that beside it indicates whether tor is running + Spuštěno: + + + + Running + + + + + Whether tor is running + Description of the value of this label, used by acccessibility tech like screen readers + + + + + + Yes + Ano + + + + + No + Ne + + + External + Externí + + + + Control connected: + Display label that beside it indicates whether ricochet is connected to tor's control port + Kontrola připojena: + + + + Control connected + + + + + Whether tor control connected + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Circuits established: + Okruhy sestaveny: + + + + Circuits established + + + + + Whether circuits established + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Hidden service: + Skrytá služba: + + + + Hidden service + + + + + Online + Online + + + + Offline + Offline + + + + Whether a hidden service is up or not + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Version: + Verze: + + + + Version + + + + + Error: <b>%1</b> + %1 is error message + Chyba: <b>%1</b> + + + + Configure + Nastavit + + + + TorStateWidget + + + + Connection failed + Připojení selhalo + + + + + Connecting… + … is ellipsis + Připojuje se... + + + + Connecting… (%1%) + %1 is progress percentage, e.g. 100 + Připojuje se… (%1%) + + + + Online + Online + + + + Connected + Připojeno + + + + Tor status + + + + + shims::ConversationModel + + + Pending + + + + + Accepted + Přijato + + + + Rejected + Zamítnuto + + + + Cancelled + + + + + Complete + + + + + Unkown Failure + + + + + Bad File Hash + + + + + Network Error + + + + + File System Error + + + + + Invalid + + + + + + Open File + + + + + + + + me + + + + + Save File + + + + diff --git a/src/ricochet-refresh/translation/ricochet_da.ts b/src/ricochet-refresh/translation/ricochet_da.ts new file mode 100644 index 00000000..041187c7 --- /dev/null +++ b/src/ricochet-refresh/translation/ricochet_da.ts @@ -0,0 +1,1257 @@ + + + + + AboutPreferences + + Ricochet %1 + %1 version, e.g. 1.0.0 + Ricochet %1 + + + + Ricochet-Refresh %1 + %1 version, e.g. 1.0.0 + + + + + Ricochet Refresh version %1 + provides a readable interpretation of the Ricochet-Refresh version number for accessibility tech like screen readers + + + + + The license of Ricochet Refresh and its dependencies + + + + + License + + + + + About + Om + + + + About page, contains license and version information + summary of the window's contents for accessibility tech like screen readers + + + + + Current Ricochet-Refresh version + + + + + AddContactDialog + + + Share your Ricochet ID to allow connection requests + tells the user the purpose of their Ricochet ID, which is basically a username + Del dit Ricochet ID for at tillade kontaktanmodninger + + + + Cancel + label for button which dismisses a dialog + Annuller + + + + Closes the contact add window + description for 'Cancel' button accessibility tech like screen readers + + + + + Add + button label to finish adding a contact/friend + Tilføj + + + + Adds the contact to your contact list + description for 'Add' button for accessibility tech like screen readres + + + + + ChatPage + + + Message area + label for accessibility tech like screen readers + + + + + Write the message to be sent here. Press enter to send + description of the text area used to send messages for accessibility tech like screen readers + + + + + ContactActions + + + Warning + + + + + Could not successfully export conversation + + + + + Open Window + Context menu command to open the chat screen in a separate window + Åbn Vindue + + + + Details... + Context menu command to open a window showing the selected contact's details + Detaljer... + + + + Rename + Context menu command to rename the selected contact + Omdøb + + + + Send File... + Context menu command to initiate a file transfer, opens a system file dialog + + + + + Export Conversation + Context menu command to initiate a chat log export, opens a system file dialog to export to + + + + + Remove + Context menu command to remove a contact from the contact list + Fjern + + + + Contact options + Description of the items in the context menu for accessibility tech like screen readers + + + + + ContactIDField + + + <b>%1</b> is already your contact + Error message showed when user attempts to add a contact already in their contact list + <b>%1</b> er allerede på din kontaktliste + + + + This ID is invalid + Error message showed when the id doesn't comply with spec https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt + + + + + You can't add yourself as a contact + Error message showed when user attempts to add themselves as a contact in their contact list + Du kan ikke tilføje dig selv som kontaktperson + + + + Enter an ID starting with <b>ricochet:</b> + Error message showed when the provided ricochet id is invalid + Skriv et ID der begynder med <b>ricochet:</b> + + + + Copied to clipboard + Message displayed when text is copied to the user's clipboard + Kopieret til clipboardet + + + + Copy + Text displayed on a button used to copy somethign to the user's clipboard + Kopiér + + + + Copies the ricochet id to the clipboard + Text description of ricochet id copy button for accessibility tech like screen readers + + + + + ContactList + + + Online + Section header in the contact list for users which are online + Online + + + + Offline + Section header in the contact list for users which are offline + Offline + + + + Requests + Section header in the contact list for users requesting to be added to the user's contact list + Anmodninger + + + + Rejected + Section header in the contact list for users that have rejected the user's request to be added to their contact list + Afvist + + + + Status for the given contacts + Description of the section header for accessibility tech like screen readers + + + + Outdated + Forældet + + + + ContactPreferences + + Date added: + Dato tilføjet: + + + Last seen: + Sidst set: + + + Request: + Anmodning: + + + Pending connection + Afventer forbindelse + + + Delivered + Afleveret + + + Accepted + Accepteret + + + Error + Fejl + + + Rejected + Afvist + + + %1 (Connected) + %1 status, e.g. "Accepted" + %1 (Forbundet) + + + Response: + Svar: + + + + Contact list + Description of the list of contacts for accessibility tech like screen readers + + + + + Contact ID for + Description of text box containing a contact's contact id for accessibility tech like screen readers + + + + + + Unknown user + A placeholder name for a contact whose name we do not know + + + + + Rename + Label for button which allows renaming of a contact + Omdøb + + + + Renames this contact + Description of button which renames a contact for accessibility tech like screen readers + + + + + Remove + Label for button which removes a contact from the contact list + Fjern + + + + Removes this contact + Description of button which removes a user from the contact list for accessibility tech like screen readers + + + + + Preferences for contact + Description of the contents of the 'Contacts' window for accessibility tech like screen readers + + + + + Contact Preferences Window + Name of the contact preferences window for accessibility tech like screen readers + + + + + A list of all your contacts, with their ricochet IDs, and options such as renaming and removing + Description of what user can do in the contact preferences window for accessibility tech like screen readers + + + + + ContactRequestDialog + + + Someone new is asking to connect to you + Descriptive text that is displayed in a popup window when a user tries to add you as a contact + Du har en ny kontaktanmodning + + + + Reject + Label for button which rejects a contact request when pressed + Afvis + + + + Rejects the incoming contact request + Description of what 'Reject' button does for accessibility tech like screen readers + + + + + Accept + Label for button which accepts a contact request when pressed + Acceptér + + + + Accepts the incoming contact request + Description of what 'Accept' button does for accessibility tech like screen readers + + + + + ContactRequestFields + + + ID: + Label for the contact id text box in the 'add new contact' window + ID: + + + + Name: + Label for the contact nickname text box in the 'add new contact' window + Navn: + + + + Field for the contact's nickname + Description of textbox for setting a contact's nickname for accessibility tech like screen readers + + + + + Message: + Label for the contact greeting message text box in the 'add new contact' window + Besked: + + + + Field for the contact's greeting message + Description of textbox for setting a new contact's initial greeting message for accessibility tech like screen readers + + + + + GeneralPreferences + + + Use a single window for conversations + Text description of an option to use one single program window for the contact list and the chats + Hold samtaler i ét vindue + + + Open links in default browser without prompting + Text description of an option to open URLs in default web browser when clicked + Åbn links i browser uden at spørge + + + + Play audio notifications + Text description of an option to play audio notifications when contacts log in, log out, and send messages + Afspil lydnotifikationer + + + + + Volume + Label for a slider used to adjust audio notification volume +---------- +Name of the slider used to adjust audio notification volume for accessibility tech like screen readers + Lydstyrke + + + + + Language + Label for combobox where users can specify the UI language +---------- +Name of the combobox used to select UI langauge for accessibility tech like screen readers + Sprog + + + + Restart Ricochet to apply changes + Genstart Ricochet for at anvende nye indstillinger + + + + What language ricochet will use + Description of what the language combox is for for accessibility tech like screen readers + + + + + LanguagePreferences + + Select Language + Vælg sprog + + + Restart Ricochet to apply changes + Genstart Ricochet for at anvende nye indstillinger + + + + LanguagesModel + + + System default + Standardindstillinger + + + + Main + + + Ricochet Error + Ricochet Fejl + + + + MainToolBar + + + + Add Contact + Tooltip text for the button that launches the dialog box for adding a new contact +---------- +Name of the button for adding a new contact for accessibility tech like screen readers + Tilføj Kontaktperson + + + + + Preferences + Tooltip text for the button that launches program preferences window +---------- +Name of the button for launching the preferences window for accessibility tech like screen readres + Indstillinger + + + + Click to add contacts + Tooltip that displays on first launch indicating how to add a new contact + Klik for at tilføje kontaktpersoner + + + + Shows the add contact dialogue + Description of the 'Add Contact' button for accessibility tech like screen readers + + + + + Shows the preferences dialogue + Description of the 'Preferences' button for accessibility tech like screen readers + + + + + MainWindow + + + Version Seperator + + + + + Remove %1 + Fjern %1 + + + + Do you want to permanently remove %1? + Vil du fjerne %1 permanent? + + + + Main Toolbar + Name of the main toolbar for accessibility tech like screen readers + + + + + Toolbar with connection status, add contact button, and preferences button + Description of the main toolbar for accessibility tech like screen readers + + + + + Contact pane + Name of the pane holding the user's contacts for accessibility tech like screen readers + + + + + MessageDelegate + + + %1 is offline + %1 nickname + %1 er offline + + + + File transfer file name + Description of the text displaying the filename of a file transfer, used by accessibility tech like screen readres + + + + + File transfer progress + Description of progress bar displaying the file transfer progress, used by accessibility tech like screen readers + + + + + File transfer status + Description of label displaying the current status of a file transfer, used by accessibility tech like screen readers + + + + + Download + Label for file transfer 'Download' button for accessibility tech like screen readers + + + + + Download file + Description of what the file transfer 'Download' button does for accessibility tech like screen readers + + + + + Cancel or reject + Label for file transfer 'Cancel' button for accessibility tech like screen readers + + + + + Cancels or rejects a file transfer + Description of what the file transfer 'Cancel' button does for accessibility tech like screen readers + + + + Copy ID + Text for context menu command to copy a ricochet contact id to clipboard + Kopiér ID + + + Copy Link + Text for context menu command to copy a url to the clipboard + Kopiér Link + + + Open with Browser + Text for context menu command to open a url in a web browser + Åbn i browser + + + Add as Contact + Tilføj som kontaktperson + + + + Copy Message + Text for context menu command to copy an entire message to clipboard + Kopiér besked + + + + Copy Selection + Text for context menu command to copy selected text to clipboard + Kopiér markeret tekst + + + + MessageDialogWrapper + + + Remove %1 + A command to remove a contact from the contact list, %1 is the contact's nickname + Fjern %1 + + + + Do you want to permanently remove %1? + Confirmation of contact removal, %1 is the contact's nickname + Vil du fjerne %1 permanent? + + + + This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. + Message describing what will happen when a contact is removed from you contact list + Denne kontaktperson vil ikke længere være i stand til at sende dig beskeder, og vil blive notificeret om fjernelsen. Kontaktpersonen kan vælge at sende dig en ny kontaktanmodning. + + + + NetworkSetupWizard + + + This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. + A label with directions for when to use the 'Connect' button + Denne computers internetadgang er uhindret. Jeg vil gerne forbinde direkte til Tor-netværket. + + + + Connect + Label for button to connect to the Tor network + Forbind + + + + This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. + A label with directions for when to use the 'Configure' button + Denne computers internetadgang er censureret, filtreret eller bag en proxy. Jeg har brug for at konfigurere netværksindstillinger. + + + + Configure + Label for button to configure the Tor daemon beore connecting to the Tor network + Konfigurér + + + + OfflineStateItem + + + Configure + Button label + Konfigurér + + + + Details + Button label + Detaljer + + + + Connection failed + Label displayed when connecting to the Tor network fails + Forbindelsen mislykkedes + + + + Connecting… + Label displayed when in process of connecting, … is ellipsis + Forbinder… + + + + OpenBrowserDialog + + <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. + Label displayed when user clicks on a link + <b>Advarsel!</b> Ved at åbne links med din browser kan du forringe din sikkerhed og anonymitet.<br><br>Du kan <a href='.'>kopiere adressen til clipboardet</a> i stedet. + + + Don't ask again for links from %1 + Spørg ikke igen om links fra %1 + + + Don't ask again for any links (not recommended!) + Checkbox option text for when user clicks on a link + Spørg ikke om nogen links (ikke anbefalt!) + + + Open Browser + Label on button to open link in a web browser + Åbn Browser + + + Cancel + Label on cancel button + Annullér + + + + PreferencesDialog + + + Ricochet Preferences + Indstillinger for Ricochet + + + + Menu Tabs + Name of the tab bar for accessibility tech like screen readers + + + + + General + Title of the general settings tab + Generelt + + + Language + Sprog + + + + Contacts + Title of the contacts list tab + Kontaktpersoner + + + + Tor + Title of the tor tab, contains tor settings and logs + Tor + + + + About + Title of the about tab, contains license information and ricochet version + Om + + + + QCocoaMenuItem + + + Preference + Indstilling + + + + StartupStatusPage + + + The Tor process was not started successfully. This is most likely an installation or system error. + Error status string displayed when tor daemon does not launch successfully + Tor-processen kunne ikke starte. Dette er sandsynligvis en installations- eller systemfejl. + + + + Quit + Button title to quit/terminate the program + Afslut + + + + TorBootstrapStatus + + + Connecting to the Tor network… + Text status displayed when tor daemon is connecting, … is ellipsis + Forbinder til Tor-netværket… + + + + Connection status + Description of the connection status text, used by accessibility tech like screen readers + + + + + Connection progress + Description of the bootstrap progress bar, used by accessibility tech like screen readers + + + + + + Back + Label for button which moves us back to previous screen + Tilbage + + + + Hide Details + Label for button which hides the tor logs + + + + + Show Details + Label for button which shows the tor logs + + + + + Hides the tor progress log + Description of hide details button, used by accessibility tech like screen readers + + + + + Shows the tor progress log + Description of show details button, used by accessibility tech like screen readers + + + + Hide details + Skjul detaljer + + + Show details + Vis detaljer + + + + Done + Label for button which closes the tor connection window + Færdig + + + + TorConfigurationPage + + + Does this computer need a proxy to access the internet? + Skal denne computer forbinde gennem en proxy for at tilgå internettet? + + + + Proxy type: + Proxy type: + + + + + None + Ingen + + + + If you need a proxy to access the internet, select one from this list. + Description used by accessibility tech, such as screen readers + + + + + Address: + Label indicating the textbox to place a proxy IP or URL + Adresse: + + + + IP address or hostname + Placeholder text of text box expecting an IP or URL for proxy + IP-adresse eller værtsnavn + + + + Enter the IP address or hostname of the proxy you wish to connect to + Description of what to enter into the IP textbox, used by accessibility tech such as screen readers + + + + + Port: + Label indicating the textbox to place a proxy port + Port: + + + + Port + Name of the port label, used by accessibility tech such as screen readers + + + + + Enter the port of the proxy you wish to connect to + Description of what to enter into the Port textbox, used by accessibility tech such as screen readers + + + + + Username: + Label indicating the textbox to place the proxy username + Brugernavn: + + + + + Optional + Textbox placeholder text indicating the field is not required + Valgfrit + + + + Username + Name of the username label, used by accessibility tech such as screen readers + + + + + If required, enter the username for the proxy you wish to connect to + Description to enter into the Username textbox, used by accessibility tech such as screen readers + + + + + Password: + Label indicating the textbox to place the proxy password + Adgangskode: + + + + Password + Name of the password label, used by accessibility tech such as screen readers + + + + + If required, enter the password for the proxy you wish to connect to + Description to enter into the Password textbox, used by accessibility tech such as screen readers + + + + + Does this computer's Internet connection go through a firewall that only allows connections to certain ports? + Description for the purpose of the Allowed Ports textbox + Går denne computers internetforbindelse gennem en firewall der kun tillader forbindelser på visse porte? + + + + Allowed ports: + Label indicating the textbox to place the allowed ports + Tilladte porte: + + + + Example: 80,443 + Textbox showing an example entry for the firewall allowed ports entry + Eksempel: 80,443 + + + + Allowed ports + Name of the allowed ports label, used by accessibility tech such as screen readers + + + + + If this computer's Internet connection is censored, you will need to obtain and use bridge relays. + Hvis denne computers internetforbindelse er censureret, er du nødt til at finde og benytte bridge relæer. + + + + + Enter one or more bridge relays (one per line): + Indsæt én eller flere bridgerelæer (én per linje): + + + + Back + Button label for going back to previous screen + Tilbage + + + + Connect + Button label for connecting to tor + Forbind + + + + TorLogDisplay + + + Tor log + Name of the text field containg the tor logs, used by accessibility tech such as screen readers + + + + + TorPreferences + + + Running: + Display label that beside it indicates whether tor is running + Kører: + + + + Running + + + + + Whether tor is running + Description of the value of this label, used by acccessibility tech like screen readers + + + + + + Yes + Ja + + + + + No + Nej + + + External + Ekstern + + + + Control connected: + Display label that beside it indicates whether ricochet is connected to tor's control port + Kontrol forbundet: + + + + Control connected + + + + + Whether tor control connected + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Circuits established: + Kredsløb etableret: + + + + Circuits established + + + + + Whether circuits established + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Hidden service: + Skjult service: + + + + Hidden service + + + + + Online + Online + + + + Offline + Offline + + + + Whether a hidden service is up or not + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Version: + Version: + + + + Version + + + + + Error: <b>%1</b> + %1 is error message + Fejl: <b>%1</b> + + + + Configure + Konfigurér + + + + TorStateWidget + + + + Connection failed + Forbindelse mislykkedes + + + + + Connecting… + … is ellipsis + Forbinder… + + + + Connecting… (%1%) + %1 is progress percentage, e.g. 100 + Forbinder… (%1) + + + + Online + Online + + + + Connected + Forbundet + + + + Tor status + + + + + shims::ConversationModel + + + Pending + + + + + Accepted + Accepteret + + + + Rejected + Afvist + + + + Cancelled + + + + + Complete + + + + + Unkown Failure + + + + + Bad File Hash + + + + + Network Error + + + + + File System Error + + + + + Invalid + + + + + + Open File + + + + + + + + me + + + + + Save File + + + + diff --git a/src/ricochet-refresh/translation/ricochet_de.ts b/src/ricochet-refresh/translation/ricochet_de.ts new file mode 100644 index 00000000..19fafa60 --- /dev/null +++ b/src/ricochet-refresh/translation/ricochet_de.ts @@ -0,0 +1,1257 @@ + + + + + AboutPreferences + + Ricochet %1 + %1 version, e.g. 1.0.0 + Ricochet %1 + + + + Ricochet-Refresh %1 + %1 version, e.g. 1.0.0 + + + + + Ricochet Refresh version %1 + provides a readable interpretation of the Ricochet-Refresh version number for accessibility tech like screen readers + + + + + The license of Ricochet Refresh and its dependencies + + + + + License + + + + + About + Über + + + + About page, contains license and version information + summary of the window's contents for accessibility tech like screen readers + + + + + Current Ricochet-Refresh version + + + + + AddContactDialog + + + Share your Ricochet ID to allow connection requests + tells the user the purpose of their Ricochet ID, which is basically a username + Teile deine Ricochet ID, um Verbindungsanfragen zu erlauben + + + + Cancel + label for button which dismisses a dialog + Abbrechen + + + + Closes the contact add window + description for 'Cancel' button accessibility tech like screen readers + + + + + Add + button label to finish adding a contact/friend + Hinzufügen + + + + Adds the contact to your contact list + description for 'Add' button for accessibility tech like screen readres + + + + + ChatPage + + + Message area + label for accessibility tech like screen readers + + + + + Write the message to be sent here. Press enter to send + description of the text area used to send messages for accessibility tech like screen readers + + + + + ContactActions + + + Warning + + + + + Could not successfully export conversation + + + + + Open Window + Context menu command to open the chat screen in a separate window + Fenster öffnen + + + + Details... + Context menu command to open a window showing the selected contact's details + Details... + + + + Rename + Context menu command to rename the selected contact + Umbenennen + + + + Send File... + Context menu command to initiate a file transfer, opens a system file dialog + + + + + Export Conversation + Context menu command to initiate a chat log export, opens a system file dialog to export to + + + + + Remove + Context menu command to remove a contact from the contact list + Entfernen + + + + Contact options + Description of the items in the context menu for accessibility tech like screen readers + + + + + ContactIDField + + + <b>%1</b> is already your contact + Error message showed when user attempts to add a contact already in their contact list + <b>%1</b> ist bereits dein Kontakt + + + + This ID is invalid + Error message showed when the id doesn't comply with spec https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt + + + + + You can't add yourself as a contact + Error message showed when user attempts to add themselves as a contact in their contact list + Du kannst dich nicht selbst als Kontakt hinzufügen + + + + Enter an ID starting with <b>ricochet:</b> + Error message showed when the provided ricochet id is invalid + Gebe eine ID an, beginnend mit <b>ricochet:<b> + + + + Copied to clipboard + Message displayed when text is copied to the user's clipboard + In die Zwischenablage kopiert + + + + Copy + Text displayed on a button used to copy somethign to the user's clipboard + Kopieren + + + + Copies the ricochet id to the clipboard + Text description of ricochet id copy button for accessibility tech like screen readers + + + + + ContactList + + + Online + Section header in the contact list for users which are online + Online + + + + Offline + Section header in the contact list for users which are offline + Offline + + + + Requests + Section header in the contact list for users requesting to be added to the user's contact list + Anfragen + + + + Rejected + Section header in the contact list for users that have rejected the user's request to be added to their contact list + Abgewiesen + + + + Status for the given contacts + Description of the section header for accessibility tech like screen readers + + + + Outdated + veraltet + + + + ContactPreferences + + Date added: + Hinzugefügt am: + + + Last seen: + Zuletzt gesehen: + + + Request: + Anfrage: + + + Pending connection + Laufende Verbindung + + + Delivered + Gesendet + + + Accepted + Akzeptiert + + + Error + Fehler + + + Rejected + Abgewiesen + + + %1 (Connected) + %1 status, e.g. "Accepted" + %1 (Verbunden) + + + Response: + Antwort: + + + + Contact list + Description of the list of contacts for accessibility tech like screen readers + + + + + Contact ID for + Description of text box containing a contact's contact id for accessibility tech like screen readers + + + + + + Unknown user + A placeholder name for a contact whose name we do not know + + + + + Rename + Label for button which allows renaming of a contact + Umbenennen + + + + Renames this contact + Description of button which renames a contact for accessibility tech like screen readers + + + + + Remove + Label for button which removes a contact from the contact list + Entfernen + + + + Removes this contact + Description of button which removes a user from the contact list for accessibility tech like screen readers + + + + + Preferences for contact + Description of the contents of the 'Contacts' window for accessibility tech like screen readers + + + + + Contact Preferences Window + Name of the contact preferences window for accessibility tech like screen readers + + + + + A list of all your contacts, with their ricochet IDs, and options such as renaming and removing + Description of what user can do in the contact preferences window for accessibility tech like screen readers + + + + + ContactRequestDialog + + + Someone new is asking to connect to you + Descriptive text that is displayed in a popup window when a user tries to add you as a contact + Jemand unbekanntes möchte sich zu Ihnen verbinden + + + + Reject + Label for button which rejects a contact request when pressed + Abweisen + + + + Rejects the incoming contact request + Description of what 'Reject' button does for accessibility tech like screen readers + + + + + Accept + Label for button which accepts a contact request when pressed + Annehmen + + + + Accepts the incoming contact request + Description of what 'Accept' button does for accessibility tech like screen readers + + + + + ContactRequestFields + + + ID: + Label for the contact id text box in the 'add new contact' window + ID: + + + + Name: + Label for the contact nickname text box in the 'add new contact' window + Name: + + + + Field for the contact's nickname + Description of textbox for setting a contact's nickname for accessibility tech like screen readers + + + + + Message: + Label for the contact greeting message text box in the 'add new contact' window + Nachricht: + + + + Field for the contact's greeting message + Description of textbox for setting a new contact's initial greeting message for accessibility tech like screen readers + + + + + GeneralPreferences + + + Use a single window for conversations + Text description of an option to use one single program window for the contact list and the chats + Nutze ein einzelnes Fenster für Unterhaltungen + + + Open links in default browser without prompting + Text description of an option to open URLs in default web browser when clicked + Öffne Links im Standardbrowser ohne Nachfrage + + + + Play audio notifications + Text description of an option to play audio notifications when contacts log in, log out, and send messages + Benachrichtigungston abspielen + + + + + Volume + Label for a slider used to adjust audio notification volume +---------- +Name of the slider used to adjust audio notification volume for accessibility tech like screen readers + Lautstärke + + + + + Language + Label for combobox where users can specify the UI language +---------- +Name of the combobox used to select UI langauge for accessibility tech like screen readers + Sprache + + + + Restart Ricochet to apply changes + Starte Ricochet neu, um Änderungung anzuwenden + + + + What language ricochet will use + Description of what the language combox is for for accessibility tech like screen readers + + + + + LanguagePreferences + + Select Language + Sprache auswählen + + + Restart Ricochet to apply changes + Starte Ricochet neu, um Änderungung anzuwenden + + + + LanguagesModel + + + System default + Systemvorgabe + + + + Main + + + Ricochet Error + Ricochet Fehler + + + + MainToolBar + + + + Add Contact + Tooltip text for the button that launches the dialog box for adding a new contact +---------- +Name of the button for adding a new contact for accessibility tech like screen readers + Kontakt hinzufügen + + + + + Preferences + Tooltip text for the button that launches program preferences window +---------- +Name of the button for launching the preferences window for accessibility tech like screen readres + Einstellungen + + + + Click to add contacts + Tooltip that displays on first launch indicating how to add a new contact + Klicken, um Kontakte hinzuzufügen + + + + Shows the add contact dialogue + Description of the 'Add Contact' button for accessibility tech like screen readers + + + + + Shows the preferences dialogue + Description of the 'Preferences' button for accessibility tech like screen readers + + + + + MainWindow + + + Version Seperator + + + + + Remove %1 + Entferne %1 + + + + Do you want to permanently remove %1? + Möchten Sie 1% permanent entfernen? + + + + Main Toolbar + Name of the main toolbar for accessibility tech like screen readers + + + + + Toolbar with connection status, add contact button, and preferences button + Description of the main toolbar for accessibility tech like screen readers + + + + + Contact pane + Name of the pane holding the user's contacts for accessibility tech like screen readers + + + + + MessageDelegate + + + %1 is offline + %1 nickname + %1 ist offline + + + + File transfer file name + Description of the text displaying the filename of a file transfer, used by accessibility tech like screen readres + + + + + File transfer progress + Description of progress bar displaying the file transfer progress, used by accessibility tech like screen readers + + + + + File transfer status + Description of label displaying the current status of a file transfer, used by accessibility tech like screen readers + + + + + Download + Label for file transfer 'Download' button for accessibility tech like screen readers + + + + + Download file + Description of what the file transfer 'Download' button does for accessibility tech like screen readers + + + + + Cancel or reject + Label for file transfer 'Cancel' button for accessibility tech like screen readers + + + + + Cancels or rejects a file transfer + Description of what the file transfer 'Cancel' button does for accessibility tech like screen readers + + + + Copy ID + Text for context menu command to copy a ricochet contact id to clipboard + Kopiere ID + + + Copy Link + Text for context menu command to copy a url to the clipboard + Kopiere Link + + + Open with Browser + Text for context menu command to open a url in a web browser + Im Browser öffnen + + + Add as Contact + Als Kontakt hinzufügen + + + + Copy Message + Text for context menu command to copy an entire message to clipboard + Nachricht kopieren + + + + Copy Selection + Text for context menu command to copy selected text to clipboard + Auswahl kopieren + + + + MessageDialogWrapper + + + Remove %1 + A command to remove a contact from the contact list, %1 is the contact's nickname + Entferne %1 + + + + Do you want to permanently remove %1? + Confirmation of contact removal, %1 is the contact's nickname + Möchten Sie %1 permanent entfernen? + + + + This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. + Message describing what will happen when a contact is removed from you contact list + Dieser Kontakt wird Ihnen nicht mehr schreiben können, und wird über das Entfernen in Kenntnis gesetzt. Eine neue Verbindungsanfrage kann von ihm/ihr gesendet werden. + + + + NetworkSetupWizard + + + This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. + A label with directions for when to use the 'Connect' button + Die Internetverbindung dieses Rechners ist frei von Hindernissen. Ich möchte gerne eine direkte Verbindung zum Tor-Netzwerk herstellen. + + + + Connect + Label for button to connect to the Tor network + Verbinden + + + + This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. + A label with directions for when to use the 'Configure' button + Die Internetverbindung dieses Rechners ist zensiert, gefiltert oder vermittelt. + + + + Configure + Label for button to configure the Tor daemon beore connecting to the Tor network + Konfigurieren + + + + OfflineStateItem + + + Configure + Button label + Konfigurieren + + + + Details + Button label + Details + + + + Connection failed + Label displayed when connecting to the Tor network fails + Verbindung gescheitert + + + + Connecting… + Label displayed when in process of connecting, … is ellipsis + Verbindet... + + + + OpenBrowserDialog + + <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. + Label displayed when user clicks on a link + <b>Warnung!</b> Links mit deinem Standardbrowser zu öffnen kann deine Sicherheit und Anonymität beeinträchtigen.<br> <br>Du kannst stattdessen <a href='.'>in die Zwischenablage kopieren.</a> + + + Don't ask again for links from %1 + Nicht mehr fragen bei Links von %1 + + + Don't ask again for any links (not recommended!) + Checkbox option text for when user clicks on a link + Für keinen Link mehr nachfragen (nicht empfohlen!) + + + Open Browser + Label on button to open link in a web browser + Browser öffnen + + + Cancel + Label on cancel button + Abbrechen + + + + PreferencesDialog + + + Ricochet Preferences + Ricochet Einstellungen + + + + Menu Tabs + Name of the tab bar for accessibility tech like screen readers + + + + + General + Title of the general settings tab + Allgemein + + + Language + Sprache + + + + Contacts + Title of the contacts list tab + Kontakte + + + + Tor + Title of the tor tab, contains tor settings and logs + Tor + + + + About + Title of the about tab, contains license information and ricochet version + Über + + + + QCocoaMenuItem + + + Preference + Einstellung + + + + StartupStatusPage + + + The Tor process was not started successfully. This is most likely an installation or system error. + Error status string displayed when tor daemon does not launch successfully + Der Tor-Prozess konnte nicht gestartet werden. Es handelt sich wahrscheinlich um einen Installations- oder einen Systemfehler. + + + + Quit + Button title to quit/terminate the program + Beenden + + + + TorBootstrapStatus + + + Connecting to the Tor network… + Text status displayed when tor daemon is connecting, … is ellipsis + Verbinde zum Tor-Netzwerk... + + + + Connection status + Description of the connection status text, used by accessibility tech like screen readers + + + + + Connection progress + Description of the bootstrap progress bar, used by accessibility tech like screen readers + + + + + + Back + Label for button which moves us back to previous screen + Zurück + + + + Hide Details + Label for button which hides the tor logs + + + + + Show Details + Label for button which shows the tor logs + + + + + Hides the tor progress log + Description of hide details button, used by accessibility tech like screen readers + + + + + Shows the tor progress log + Description of show details button, used by accessibility tech like screen readers + + + + Hide details + Details verstecken + + + Show details + Details anzeigen + + + + Done + Label for button which closes the tor connection window + Fertig + + + + TorConfigurationPage + + + Does this computer need a proxy to access the internet? + Benötigt dieser Rechner einen Proxy um sich mit dem Internet zu verbinden? + + + + Proxy type: + Proxytyp: + + + + + None + Keiner + + + + If you need a proxy to access the internet, select one from this list. + Description used by accessibility tech, such as screen readers + + + + + Address: + Label indicating the textbox to place a proxy IP or URL + Adresse: + + + + IP address or hostname + Placeholder text of text box expecting an IP or URL for proxy + IP-Adresse oder Rechnername + + + + Enter the IP address or hostname of the proxy you wish to connect to + Description of what to enter into the IP textbox, used by accessibility tech such as screen readers + + + + + Port: + Label indicating the textbox to place a proxy port + Port: + + + + Port + Name of the port label, used by accessibility tech such as screen readers + + + + + Enter the port of the proxy you wish to connect to + Description of what to enter into the Port textbox, used by accessibility tech such as screen readers + + + + + Username: + Label indicating the textbox to place the proxy username + Benutzername: + + + + + Optional + Textbox placeholder text indicating the field is not required + Fakultativ + + + + Username + Name of the username label, used by accessibility tech such as screen readers + + + + + If required, enter the username for the proxy you wish to connect to + Description to enter into the Username textbox, used by accessibility tech such as screen readers + + + + + Password: + Label indicating the textbox to place the proxy password + Passwort: + + + + Password + Name of the password label, used by accessibility tech such as screen readers + + + + + If required, enter the password for the proxy you wish to connect to + Description to enter into the Password textbox, used by accessibility tech such as screen readers + + + + + Does this computer's Internet connection go through a firewall that only allows connections to certain ports? + Description for the purpose of the Allowed Ports textbox + Geht die Verbindung dieses Rechners durch eine Firewall, die nur Verbindungen zu manchen Ports erlaubt? + + + + Allowed ports: + Label indicating the textbox to place the allowed ports + Erlaubte Ports: + + + + Example: 80,443 + Textbox showing an example entry for the firewall allowed ports entry + Beispiel: 80, 443 + + + + Allowed ports + Name of the allowed ports label, used by accessibility tech such as screen readers + + + + + If this computer's Internet connection is censored, you will need to obtain and use bridge relays. + Sollte die Internetverbindung dieses Rechners zensiert sein, werden Sie Brücken-Relays finden und nutzen müssen. + + + + + Enter one or more bridge relays (one per line): + Geben Sie ein oder mehrere Brücken-Relays an (eins pro Zeile): + + + + Back + Button label for going back to previous screen + Zurück + + + + Connect + Button label for connecting to tor + Verbinden + + + + TorLogDisplay + + + Tor log + Name of the text field containg the tor logs, used by accessibility tech such as screen readers + + + + + TorPreferences + + + Running: + Display label that beside it indicates whether tor is running + Läuft: + + + + Running + + + + + Whether tor is running + Description of the value of this label, used by acccessibility tech like screen readers + + + + + + Yes + Ja + + + + + No + Nein + + + External + Extern + + + + Control connected: + Display label that beside it indicates whether ricochet is connected to tor's control port + Control verbunden: + + + + Control connected + + + + + Whether tor control connected + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Circuits established: + Circuits aufgebaut: + + + + Circuits established + + + + + Whether circuits established + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Hidden service: + Versteckter Service: + + + + Hidden service + + + + + Online + Online + + + + Offline + Offline + + + + Whether a hidden service is up or not + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Version: + Version: + + + + Version + + + + + Error: <b>%1</b> + %1 is error message + Fehler: <b>%1</b> + + + + Configure + Konfigurieren + + + + TorStateWidget + + + + Connection failed + Verbindung gescheitert + + + + + Connecting… + … is ellipsis + Verbinde... + + + + Connecting… (%1%) + %1 is progress percentage, e.g. 100 + Verbinde... (%1%) + + + + Online + Online + + + + Connected + Verbunden + + + + Tor status + + + + + shims::ConversationModel + + + Pending + + + + + Accepted + Akzeptiert + + + + Rejected + Abgewiesen + + + + Cancelled + + + + + Complete + + + + + Unkown Failure + + + + + Bad File Hash + + + + + Network Error + + + + + File System Error + + + + + Invalid + + + + + + Open File + + + + + + + + me + + + + + Save File + + + + diff --git a/src/ricochet-refresh/translation/ricochet_en.ts b/src/ricochet-refresh/translation/ricochet_en.ts new file mode 100644 index 00000000..ce65d9e5 --- /dev/null +++ b/src/ricochet-refresh/translation/ricochet_en.ts @@ -0,0 +1,1129 @@ + + + + + AboutPreferences + + + Ricochet-Refresh %1 + %1 version, e.g. 1.0.0 + + + + + Ricochet Refresh version %1 + provides a readable interpretation of the Ricochet-Refresh version number for accessibility tech like screen readers + + + + + The license of Ricochet Refresh and its dependencies + + + + + License + + + + + About + + + + + About page, contains license and version information + summary of the window's contents for accessibility tech like screen readers + + + + + Current Ricochet-Refresh version + + + + + AddContactDialog + + + Share your Ricochet ID to allow connection requests + tells the user the purpose of their Ricochet ID, which is basically a username + + + + + Cancel + label for button which dismisses a dialog + + + + + Closes the contact add window + description for 'Cancel' button accessibility tech like screen readers + + + + + Add + button label to finish adding a contact/friend + + + + + Adds the contact to your contact list + description for 'Add' button for accessibility tech like screen readres + + + + + ChatPage + + + Message area + label for accessibility tech like screen readers + + + + + Write the message to be sent here. Press enter to send + description of the text area used to send messages for accessibility tech like screen readers + + + + + ContactActions + + + Warning + + + + + Could not successfully export conversation + + + + + Open Window + Context menu command to open the chat screen in a separate window + + + + + Details... + Context menu command to open a window showing the selected contact's details + + + + + Rename + Context menu command to rename the selected contact + + + + + Send File... + Context menu command to initiate a file transfer, opens a system file dialog + + + + + Export Conversation + Context menu command to initiate a chat log export, opens a system file dialog to export to + + + + + Remove + Context menu command to remove a contact from the contact list + + + + + Contact options + Description of the items in the context menu for accessibility tech like screen readers + + + + + ContactIDField + + + <b>%1</b> is already your contact + Error message showed when user attempts to add a contact already in their contact list + + + + + This ID is invalid + Error message showed when the id doesn't comply with spec https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt + + + + + You can't add yourself as a contact + Error message showed when user attempts to add themselves as a contact in their contact list + + + + + Enter an ID starting with <b>ricochet:</b> + Error message showed when the provided ricochet id is invalid + + + + + Copied to clipboard + Message displayed when text is copied to the user's clipboard + + + + + Copy + Text displayed on a button used to copy somethign to the user's clipboard + + + + + Copies the ricochet id to the clipboard + Text description of ricochet id copy button for accessibility tech like screen readers + + + + + ContactList + + + Online + Section header in the contact list for users which are online + + + + + Offline + Section header in the contact list for users which are offline + + + + + Requests + Section header in the contact list for users requesting to be added to the user's contact list + + + + + Rejected + Section header in the contact list for users that have rejected the user's request to be added to their contact list + + + + + Status for the given contacts + Description of the section header for accessibility tech like screen readers + + + + + ContactPreferences + + + Contact list + Description of the list of contacts for accessibility tech like screen readers + + + + + Contact ID for + Description of text box containing a contact's contact id for accessibility tech like screen readers + + + + + + Unknown user + A placeholder name for a contact whose name we do not know + + + + + Rename + Label for button which allows renaming of a contact + + + + + Renames this contact + Description of button which renames a contact for accessibility tech like screen readers + + + + + Remove + Label for button which removes a contact from the contact list + + + + + Removes this contact + Description of button which removes a user from the contact list for accessibility tech like screen readers + + + + + Preferences for contact + Description of the contents of the 'Contacts' window for accessibility tech like screen readers + + + + + Contact Preferences Window + Name of the contact preferences window for accessibility tech like screen readers + + + + + A list of all your contacts, with their ricochet IDs, and options such as renaming and removing + Description of what user can do in the contact preferences window for accessibility tech like screen readers + + + + + ContactRequestDialog + + + Someone new is asking to connect to you + Descriptive text that is displayed in a popup window when a user tries to add you as a contact + + + + + Reject + Label for button which rejects a contact request when pressed + + + + + Rejects the incoming contact request + Description of what 'Reject' button does for accessibility tech like screen readers + + + + + Accept + Label for button which accepts a contact request when pressed + + + + + Accepts the incoming contact request + Description of what 'Accept' button does for accessibility tech like screen readers + + + + + ContactRequestFields + + + ID: + Label for the contact id text box in the 'add new contact' window + + + + + Name: + Label for the contact nickname text box in the 'add new contact' window + + + + + Field for the contact's nickname + Description of textbox for setting a contact's nickname for accessibility tech like screen readers + + + + + Message: + Label for the contact greeting message text box in the 'add new contact' window + + + + + Field for the contact's greeting message + Description of textbox for setting a new contact's initial greeting message for accessibility tech like screen readers + + + + + GeneralPreferences + + + Use a single window for conversations + Text description of an option to use one single program window for the contact list and the chats + + + + + Play audio notifications + Text description of an option to play audio notifications when contacts log in, log out, and send messages + + + + + + Volume + Label for a slider used to adjust audio notification volume +---------- +Name of the slider used to adjust audio notification volume for accessibility tech like screen readers + + + + + + Language + Label for combobox where users can specify the UI language +---------- +Name of the combobox used to select UI langauge for accessibility tech like screen readers + + + + + Restart Ricochet to apply changes + + + + + What language ricochet will use + Description of what the language combox is for for accessibility tech like screen readers + + + + + LanguagesModel + + + System default + + + + + Main + + + Ricochet Error + + + + + MainToolBar + + + + Add Contact + Tooltip text for the button that launches the dialog box for adding a new contact +---------- +Name of the button for adding a new contact for accessibility tech like screen readers + + + + + + Preferences + Tooltip text for the button that launches program preferences window +---------- +Name of the button for launching the preferences window for accessibility tech like screen readres + + + + + Click to add contacts + Tooltip that displays on first launch indicating how to add a new contact + + + + + Shows the add contact dialogue + Description of the 'Add Contact' button for accessibility tech like screen readers + + + + + Shows the preferences dialogue + Description of the 'Preferences' button for accessibility tech like screen readers + + + + + MainWindow + + + Version Seperator + dot + + + + Remove %1 + + + + + Do you want to permanently remove %1? + + + + + Main Toolbar + Name of the main toolbar for accessibility tech like screen readers + + + + + Toolbar with connection status, add contact button, and preferences button + Description of the main toolbar for accessibility tech like screen readers + + + + + Contact pane + Name of the pane holding the user's contacts for accessibility tech like screen readers + + + + + MessageDelegate + + + %1 is offline + %1 nickname + + + + + File transfer file name + Description of the text displaying the filename of a file transfer, used by accessibility tech like screen readres + + + + + File transfer progress + Description of progress bar displaying the file transfer progress, used by accessibility tech like screen readers + + + + + File transfer status + Description of label displaying the current status of a file transfer, used by accessibility tech like screen readers + + + + + Download + Label for file transfer 'Download' button for accessibility tech like screen readers + + + + + Download file + Description of what the file transfer 'Download' button does for accessibility tech like screen readers + + + + + Cancel or reject + Label for file transfer 'Cancel' button for accessibility tech like screen readers + + + + + Cancels or rejects a file transfer + Description of what the file transfer 'Cancel' button does for accessibility tech like screen readers + + + + + Copy Message + Text for context menu command to copy an entire message to clipboard + + + + + Copy Selection + Text for context menu command to copy selected text to clipboard + + + + + MessageDialogWrapper + + + Remove %1 + A command to remove a contact from the contact list, %1 is the contact's nickname + + + + + Do you want to permanently remove %1? + Confirmation of contact removal, %1 is the contact's nickname + + + + + This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. + Message describing what will happen when a contact is removed from you contact list + + + + + NetworkSetupWizard + + + This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. + A label with directions for when to use the 'Connect' button + + + + + Connect + Label for button to connect to the Tor network + + + + + This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. + A label with directions for when to use the 'Configure' button + + + + + Configure + Label for button to configure the Tor daemon beore connecting to the Tor network + + + + + OfflineStateItem + + + Configure + Button label + + + + + Details + Button label + + + + + Connection failed + Label displayed when connecting to the Tor network fails + + + + + Connecting… + Label displayed when in process of connecting, … is ellipsis + + + + + PreferencesDialog + + + Ricochet Preferences + + + + + Menu Tabs + Name of the tab bar for accessibility tech like screen readers + + + + + General + Title of the general settings tab + + + + + Contacts + Title of the contacts list tab + + + + + Tor + Title of the tor tab, contains tor settings and logs + + + + + About + Title of the about tab, contains license information and ricochet version + + + + + QCocoaMenuItem + + + Preference + + + + + StartupStatusPage + + + The Tor process was not started successfully. This is most likely an installation or system error. + Error status string displayed when tor daemon does not launch successfully + + + + + Quit + Button title to quit/terminate the program + + + + + TorBootstrapStatus + + + Connecting to the Tor network… + Text status displayed when tor daemon is connecting, … is ellipsis + + + + + Connection status + Description of the connection status text, used by accessibility tech like screen readers + + + + + Connection progress + Description of the bootstrap progress bar, used by accessibility tech like screen readers + + + + + + Back + Label for button which moves us back to previous screen + + + + + Hide Details + Label for button which hides the tor logs + + + + + Show Details + Label for button which shows the tor logs + + + + + Hides the tor progress log + Description of hide details button, used by accessibility tech like screen readers + + + + + Shows the tor progress log + Description of show details button, used by accessibility tech like screen readers + + + + + Done + Label for button which closes the tor connection window + + + + + TorConfigurationPage + + + Does this computer need a proxy to access the internet? + + + + + Proxy type: + + + + + + None + + + + + If you need a proxy to access the internet, select one from this list. + Description used by accessibility tech, such as screen readers + + + + + Address: + Label indicating the textbox to place a proxy IP or URL + + + + + IP address or hostname + Placeholder text of text box expecting an IP or URL for proxy + + + + + Enter the IP address or hostname of the proxy you wish to connect to + Description of what to enter into the IP textbox, used by accessibility tech such as screen readers + + + + + Port: + Label indicating the textbox to place a proxy port + + + + + Port + Name of the port label, used by accessibility tech such as screen readers + + + + + Enter the port of the proxy you wish to connect to + Description of what to enter into the Port textbox, used by accessibility tech such as screen readers + + + + + Username: + Label indicating the textbox to place the proxy username + + + + + + Optional + Textbox placeholder text indicating the field is not required + + + + + Username + Name of the username label, used by accessibility tech such as screen readers + + + + + If required, enter the username for the proxy you wish to connect to + Description to enter into the Username textbox, used by accessibility tech such as screen readers + + + + + Password: + Label indicating the textbox to place the proxy password + + + + + Password + Name of the password label, used by accessibility tech such as screen readers + + + + + If required, enter the password for the proxy you wish to connect to + Description to enter into the Password textbox, used by accessibility tech such as screen readers + + + + + Does this computer's Internet connection go through a firewall that only allows connections to certain ports? + Description for the purpose of the Allowed Ports textbox + + + + + Allowed ports: + Label indicating the textbox to place the allowed ports + + + + + Example: 80,443 + Textbox showing an example entry for the firewall allowed ports entry + + + + + Allowed ports + Name of the allowed ports label, used by accessibility tech such as screen readers + + + + + If this computer's Internet connection is censored, you will need to obtain and use bridge relays. + + + + + + Enter one or more bridge relays (one per line): + + + + + Back + Button label for going back to previous screen + + + + + Connect + Button label for connecting to tor + + + + + TorLogDisplay + + + Tor log + Name of the text field containg the tor logs, used by accessibility tech such as screen readers + + + + + TorPreferences + + + Running: + Display label that beside it indicates whether tor is running + + + + + + Yes + + + + + + No + + + + + Control connected: + Display label that beside it indicates whether ricochet is connected to tor's control port + + + + + Running + + + + + Whether tor is running + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Control connected + + + + + Whether tor control connected + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Circuits established: + + + + + Circuits established + + + + + Whether circuits established + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Hidden service: + + + + + Hidden service + + + + + Online + + + + + Offline + + + + + Whether a hidden service is up or not + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Version: + + + + + Version + + + + + Error: <b>%1</b> + %1 is error message + + + + + Configure + + + + + TorStateWidget + + + + Connection failed + + + + + + Connecting… + … is ellipsis + + + + + Connecting… (%1%) + %1 is progress percentage, e.g. 100 + + + + + Online + + + + + Connected + + + + + Tor status + + + + + shims::ConversationModel + + + Pending + + + + + Accepted + + + + + Rejected + + + + + Cancelled + + + + + Complete + + + + + Unkown Failure + + + + + Bad File Hash + + + + + Network Error + + + + + File System Error + + + + + Invalid + + + + + + Open File + + + + + + + + me + + + + + Save File + + + + diff --git a/src/ricochet-refresh/translation/ricochet_es.ts b/src/ricochet-refresh/translation/ricochet_es.ts new file mode 100644 index 00000000..cbabee3e --- /dev/null +++ b/src/ricochet-refresh/translation/ricochet_es.ts @@ -0,0 +1,1257 @@ + + + + + AboutPreferences + + Ricochet %1 + %1 version, e.g. 1.0.0 + Ricochet %1 + + + + Ricochet-Refresh %1 + %1 version, e.g. 1.0.0 + + + + + Ricochet Refresh version %1 + provides a readable interpretation of the Ricochet-Refresh version number for accessibility tech like screen readers + + + + + The license of Ricochet Refresh and its dependencies + + + + + License + + + + + About + Acerca de + + + + About page, contains license and version information + summary of the window's contents for accessibility tech like screen readers + + + + + Current Ricochet-Refresh version + + + + + AddContactDialog + + + Share your Ricochet ID to allow connection requests + tells the user the purpose of their Ricochet ID, which is basically a username + Comparta su ID (identificador) de Ricochet para permitir solicitudes de conexión + + + + Cancel + label for button which dismisses a dialog + Cancelar + + + + Closes the contact add window + description for 'Cancel' button accessibility tech like screen readers + + + + + Add + button label to finish adding a contact/friend + Añadir + + + + Adds the contact to your contact list + description for 'Add' button for accessibility tech like screen readres + + + + + ChatPage + + + Message area + label for accessibility tech like screen readers + + + + + Write the message to be sent here. Press enter to send + description of the text area used to send messages for accessibility tech like screen readers + + + + + ContactActions + + + Warning + + + + + Could not successfully export conversation + + + + + Open Window + Context menu command to open the chat screen in a separate window + Abrir Ventana + + + + Details... + Context menu command to open a window showing the selected contact's details + Detalles... + + + + Rename + Context menu command to rename the selected contact + Renombrar + + + + Send File... + Context menu command to initiate a file transfer, opens a system file dialog + + + + + Export Conversation + Context menu command to initiate a chat log export, opens a system file dialog to export to + + + + + Remove + Context menu command to remove a contact from the contact list + Eliminar + + + + Contact options + Description of the items in the context menu for accessibility tech like screen readers + + + + + ContactIDField + + + <b>%1</b> is already your contact + Error message showed when user attempts to add a contact already in their contact list + <b>%1</b> ya es su contacto + + + + This ID is invalid + Error message showed when the id doesn't comply with spec https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt + + + + + You can't add yourself as a contact + Error message showed when user attempts to add themselves as a contact in their contact list + No puede añadirse a usted mismo como un contacto + + + + Enter an ID starting with <b>ricochet:</b> + Error message showed when the provided ricochet id is invalid + Introduzca un ID comience con <b>ricochet:</b> + + + + Copied to clipboard + Message displayed when text is copied to the user's clipboard + Copiado al portapapeles + + + + Copy + Text displayed on a button used to copy somethign to the user's clipboard + Copiar + + + + Copies the ricochet id to the clipboard + Text description of ricochet id copy button for accessibility tech like screen readers + + + + + ContactList + + + Online + Section header in the contact list for users which are online + En línea + + + + Offline + Section header in the contact list for users which are offline + Fuera de línea + + + + Requests + Section header in the contact list for users requesting to be added to the user's contact list + Solicitudes + + + + Rejected + Section header in the contact list for users that have rejected the user's request to be added to their contact list + Rechazada + + + + Status for the given contacts + Description of the section header for accessibility tech like screen readers + + + + Outdated + Caducada + + + + ContactPreferences + + Date added: + Añadido desde: + + + Last seen: + Visto por última vez: + + + Request: + Solicitud: + + + Pending connection + Conexión pendiente + + + Delivered + Entregado + + + Accepted + Aceptado + + + Error + Error + + + Rejected + Rechazado + + + %1 (Connected) + %1 status, e.g. "Accepted" + %1 (Conectado) + + + Response: + Respuesta: + + + + Contact list + Description of the list of contacts for accessibility tech like screen readers + + + + + Contact ID for + Description of text box containing a contact's contact id for accessibility tech like screen readers + + + + + + Unknown user + A placeholder name for a contact whose name we do not know + + + + + Rename + Label for button which allows renaming of a contact + Renombrar + + + + Renames this contact + Description of button which renames a contact for accessibility tech like screen readers + + + + + Remove + Label for button which removes a contact from the contact list + Eliminar + + + + Removes this contact + Description of button which removes a user from the contact list for accessibility tech like screen readers + + + + + Preferences for contact + Description of the contents of the 'Contacts' window for accessibility tech like screen readers + + + + + Contact Preferences Window + Name of the contact preferences window for accessibility tech like screen readers + + + + + A list of all your contacts, with their ricochet IDs, and options such as renaming and removing + Description of what user can do in the contact preferences window for accessibility tech like screen readers + + + + + ContactRequestDialog + + + Someone new is asking to connect to you + Descriptive text that is displayed in a popup window when a user tries to add you as a contact + Alguien nuevo está pidiendo conectarse con usted + + + + Reject + Label for button which rejects a contact request when pressed + Rechazar + + + + Rejects the incoming contact request + Description of what 'Reject' button does for accessibility tech like screen readers + + + + + Accept + Label for button which accepts a contact request when pressed + Aceptar + + + + Accepts the incoming contact request + Description of what 'Accept' button does for accessibility tech like screen readers + + + + + ContactRequestFields + + + ID: + Label for the contact id text box in the 'add new contact' window + ID (identificador): + + + + Name: + Label for the contact nickname text box in the 'add new contact' window + Nombre: + + + + Field for the contact's nickname + Description of textbox for setting a contact's nickname for accessibility tech like screen readers + + + + + Message: + Label for the contact greeting message text box in the 'add new contact' window + Mensaje: + + + + Field for the contact's greeting message + Description of textbox for setting a new contact's initial greeting message for accessibility tech like screen readers + + + + + GeneralPreferences + + + Use a single window for conversations + Text description of an option to use one single program window for the contact list and the chats + Usar una única ventana para conversaciones + + + Open links in default browser without prompting + Text description of an option to open URLs in default web browser when clicked + Abrir enlaces en el navegador predeterminado sin pedir confirmación + + + + Play audio notifications + Text description of an option to play audio notifications when contacts log in, log out, and send messages + Reproducir notificaciones de audio + + + + + Volume + Label for a slider used to adjust audio notification volume +---------- +Name of the slider used to adjust audio notification volume for accessibility tech like screen readers + Volumen + + + + + Language + Label for combobox where users can specify the UI language +---------- +Name of the combobox used to select UI langauge for accessibility tech like screen readers + Idioma + + + + Restart Ricochet to apply changes + Reiniciar Ricochet para aplicar los cambios + + + + What language ricochet will use + Description of what the language combox is for for accessibility tech like screen readers + + + + + LanguagePreferences + + Select Language + Seleccionar idioma + + + Restart Ricochet to apply changes + Reiniciar Ricochet para aplicar los cambios + + + + LanguagesModel + + + System default + Predeterminado del sistema + + + + Main + + + Ricochet Error + Error de Ricochet + + + + MainToolBar + + + + Add Contact + Tooltip text for the button that launches the dialog box for adding a new contact +---------- +Name of the button for adding a new contact for accessibility tech like screen readers + Añadir Contacto + + + + + Preferences + Tooltip text for the button that launches program preferences window +---------- +Name of the button for launching the preferences window for accessibility tech like screen readres + Preferencias + + + + Click to add contacts + Tooltip that displays on first launch indicating how to add a new contact + Haga clic para añadir contactos + + + + Shows the add contact dialogue + Description of the 'Add Contact' button for accessibility tech like screen readers + + + + + Shows the preferences dialogue + Description of the 'Preferences' button for accessibility tech like screen readers + + + + + MainWindow + + + Version Seperator + + + + + Remove %1 + Eliminar %1 + + + + Do you want to permanently remove %1? + ¿Quiere eliminar permanentemente a %1? + + + + Main Toolbar + Name of the main toolbar for accessibility tech like screen readers + + + + + Toolbar with connection status, add contact button, and preferences button + Description of the main toolbar for accessibility tech like screen readers + + + + + Contact pane + Name of the pane holding the user's contacts for accessibility tech like screen readers + + + + + MessageDelegate + + + %1 is offline + %1 nickname + %1 está fuera de línea + + + + File transfer file name + Description of the text displaying the filename of a file transfer, used by accessibility tech like screen readres + + + + + File transfer progress + Description of progress bar displaying the file transfer progress, used by accessibility tech like screen readers + + + + + File transfer status + Description of label displaying the current status of a file transfer, used by accessibility tech like screen readers + + + + + Download + Label for file transfer 'Download' button for accessibility tech like screen readers + + + + + Download file + Description of what the file transfer 'Download' button does for accessibility tech like screen readers + + + + + Cancel or reject + Label for file transfer 'Cancel' button for accessibility tech like screen readers + + + + + Cancels or rejects a file transfer + Description of what the file transfer 'Cancel' button does for accessibility tech like screen readers + + + + Copy ID + Text for context menu command to copy a ricochet contact id to clipboard + Copiar ID (identificador) + + + Copy Link + Text for context menu command to copy a url to the clipboard + Copiar Enlace + + + Open with Browser + Text for context menu command to open a url in a web browser + Abrir con el Navegador + + + Add as Contact + Añadir como Contacto + + + + Copy Message + Text for context menu command to copy an entire message to clipboard + Copiar mensaje + + + + Copy Selection + Text for context menu command to copy selected text to clipboard + Copiar selección + + + + MessageDialogWrapper + + + Remove %1 + A command to remove a contact from the contact list, %1 is the contact's nickname + Eliminar %1 + + + + Do you want to permanently remove %1? + Confirmation of contact removal, %1 is the contact's nickname + ¿Quiere eliminar permanentemente a %1? + + + + This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. + Message describing what will happen when a contact is removed from you contact list + Este contacto ya no podrá enviarle mensajes, y será notificado sobre la eliminación. Pueden elegir enviar una nueva solicitud de conexión. + + + + NetworkSetupWizard + + + This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. + A label with directions for when to use the 'Connect' button + La conexión a Internet de este equipo está libre de obstáculos. Me gustaría conectar directamente a la red Tor. + + + + Connect + Label for button to connect to the Tor network + Conectar + + + + This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. + A label with directions for when to use the 'Configure' button + La conexión a Internet de este equipo está censurada, filtrada, o proxyficada. Necesito configurar las preferencias de red. + + + + Configure + Label for button to configure the Tor daemon beore connecting to the Tor network + Configurar + + + + OfflineStateItem + + + Configure + Button label + Configurar + + + + Details + Button label + Detalles + + + + Connection failed + Label displayed when connecting to the Tor network fails + Conexión fallida + + + + Connecting… + Label displayed when in process of connecting, … is ellipsis + Conectando... + + + + OpenBrowserDialog + + <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. + Label displayed when user clicks on a link + <b>¡Advertencia!</b> Abrir enlaces con su navegador predeterminado perjudicará su seguridad y anonimato. <br><br>En su lugar puede <a href='.'>copiarlos al portapapeles</a>. + + + Don't ask again for links from %1 + No volver a preguntar por enlaces desde %1 + + + Don't ask again for any links (not recommended!) + Checkbox option text for when user clicks on a link + No volver a preguntar por ningún enlace (¡no recomendado!) + + + Open Browser + Label on button to open link in a web browser + Abrir Navegador + + + Cancel + Label on cancel button + Cancelar + + + + PreferencesDialog + + + Ricochet Preferences + Preferencias de Ricochet + + + + Menu Tabs + Name of the tab bar for accessibility tech like screen readers + + + + + General + Title of the general settings tab + General + + + Language + Idioma + + + + Contacts + Title of the contacts list tab + Contactos + + + + Tor + Title of the tor tab, contains tor settings and logs + Tor + + + + About + Title of the about tab, contains license information and ricochet version + Acerca de + + + + QCocoaMenuItem + + + Preference + Preferencia + + + + StartupStatusPage + + + The Tor process was not started successfully. This is most likely an installation or system error. + Error status string displayed when tor daemon does not launch successfully + El proceso Tor no se inició con éxito. Lo más probable es que esto sea un error de instalación o de sistema. + + + + Quit + Button title to quit/terminate the program + Salir + + + + TorBootstrapStatus + + + Connecting to the Tor network… + Text status displayed when tor daemon is connecting, … is ellipsis + Conectando a la red Tor... + + + + Connection status + Description of the connection status text, used by accessibility tech like screen readers + + + + + Connection progress + Description of the bootstrap progress bar, used by accessibility tech like screen readers + + + + + + Back + Label for button which moves us back to previous screen + Atrás + + + + Hide Details + Label for button which hides the tor logs + + + + + Show Details + Label for button which shows the tor logs + + + + + Hides the tor progress log + Description of hide details button, used by accessibility tech like screen readers + + + + + Shows the tor progress log + Description of show details button, used by accessibility tech like screen readers + + + + Hide details + Ocultar detalles + + + Show details + Mostrar detalles + + + + Done + Label for button which closes the tor connection window + Hecho + + + + TorConfigurationPage + + + Does this computer need a proxy to access the internet? + ¿Este equipo necesita un proxy para acceder a Internet? + + + + Proxy type: + Tipo de proxy: + + + + + None + Ninguno + + + + If you need a proxy to access the internet, select one from this list. + Description used by accessibility tech, such as screen readers + + + + + Address: + Label indicating the textbox to place a proxy IP or URL + Dirección: + + + + IP address or hostname + Placeholder text of text box expecting an IP or URL for proxy + Dirección IP o nombre del equipo + + + + Enter the IP address or hostname of the proxy you wish to connect to + Description of what to enter into the IP textbox, used by accessibility tech such as screen readers + + + + + Port: + Label indicating the textbox to place a proxy port + Puerto: + + + + Port + Name of the port label, used by accessibility tech such as screen readers + + + + + Enter the port of the proxy you wish to connect to + Description of what to enter into the Port textbox, used by accessibility tech such as screen readers + + + + + Username: + Label indicating the textbox to place the proxy username + Nombre de usuario: + + + + + Optional + Textbox placeholder text indicating the field is not required + Opcional + + + + Username + Name of the username label, used by accessibility tech such as screen readers + + + + + If required, enter the username for the proxy you wish to connect to + Description to enter into the Username textbox, used by accessibility tech such as screen readers + + + + + Password: + Label indicating the textbox to place the proxy password + Contraseña: + + + + Password + Name of the password label, used by accessibility tech such as screen readers + + + + + If required, enter the password for the proxy you wish to connect to + Description to enter into the Password textbox, used by accessibility tech such as screen readers + + + + + Does this computer's Internet connection go through a firewall that only allows connections to certain ports? + Description for the purpose of the Allowed Ports textbox + ¿La conexión a Internet de este equipo va a través de un cortafuegos (firewall) que sólo permite conexiones a ciertos puertos? + + + + Allowed ports: + Label indicating the textbox to place the allowed ports + Puertos permitidos: + + + + Example: 80,443 + Textbox showing an example entry for the firewall allowed ports entry + Ejemplo: 80,443 + + + + Allowed ports + Name of the allowed ports label, used by accessibility tech such as screen readers + + + + + If this computer's Internet connection is censored, you will need to obtain and use bridge relays. + Si la conexión a Internet de este equipo está bajo censura, necesitará obtener y usar repetidores puente (bridge relays). + + + + + Enter one or more bridge relays (one per line): + Introduzca uno o más repetidores puente (uno por línea): + + + + Back + Button label for going back to previous screen + Atrás + + + + Connect + Button label for connecting to tor + Conectar + + + + TorLogDisplay + + + Tor log + Name of the text field containg the tor logs, used by accessibility tech such as screen readers + + + + + TorPreferences + + + Running: + Display label that beside it indicates whether tor is running + Ejecutar: + + + + Running + + + + + Whether tor is running + Description of the value of this label, used by acccessibility tech like screen readers + + + + + + Yes + + + + + + No + No + + + External + Externo + + + + Control connected: + Display label that beside it indicates whether ricochet is connected to tor's control port + Control conectado: + + + + Control connected + + + + + Whether tor control connected + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Circuits established: + Circuitos establecidos: + + + + Circuits established + + + + + Whether circuits established + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Hidden service: + Servicio oculto: + + + + Hidden service + + + + + Online + En línea + + + + Offline + Fuera de línea + + + + Whether a hidden service is up or not + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Version: + Versión: + + + + Version + + + + + Error: <b>%1</b> + %1 is error message + Error: <b>%1</b> + + + + Configure + Configurar + + + + TorStateWidget + + + + Connection failed + Conexión fallida + + + + + Connecting… + … is ellipsis + Conectando... + + + + Connecting… (%1%) + %1 is progress percentage, e.g. 100 + Conectando... (%1%) + + + + Online + En línea + + + + Connected + Conectado + + + + Tor status + + + + + shims::ConversationModel + + + Pending + + + + + Accepted + Aceptado + + + + Rejected + + + + + Cancelled + + + + + Complete + + + + + Unkown Failure + + + + + Bad File Hash + + + + + Network Error + + + + + File System Error + + + + + Invalid + + + + + + Open File + + + + + + + + me + + + + + Save File + + + + diff --git a/src/ricochet-refresh/translation/ricochet_et_EE.ts b/src/ricochet-refresh/translation/ricochet_et_EE.ts new file mode 100644 index 00000000..75e724a7 --- /dev/null +++ b/src/ricochet-refresh/translation/ricochet_et_EE.ts @@ -0,0 +1,1257 @@ + + + + + AboutPreferences + + Ricochet %1 + %1 version, e.g. 1.0.0 + Ricochet %1 + + + + Ricochet-Refresh %1 + %1 version, e.g. 1.0.0 + + + + + Ricochet Refresh version %1 + provides a readable interpretation of the Ricochet-Refresh version number for accessibility tech like screen readers + + + + + The license of Ricochet Refresh and its dependencies + + + + + License + + + + + About + Lähemalt + + + + About page, contains license and version information + summary of the window's contents for accessibility tech like screen readers + + + + + Current Ricochet-Refresh version + + + + + AddContactDialog + + + Share your Ricochet ID to allow connection requests + tells the user the purpose of their Ricochet ID, which is basically a username + Jaga oma Ricochet ID ühenduste jaoks + + + + Cancel + label for button which dismisses a dialog + Tühista + + + + Closes the contact add window + description for 'Cancel' button accessibility tech like screen readers + + + + + Add + button label to finish adding a contact/friend + Lisa + + + + Adds the contact to your contact list + description for 'Add' button for accessibility tech like screen readres + + + + + ChatPage + + + Message area + label for accessibility tech like screen readers + + + + + Write the message to be sent here. Press enter to send + description of the text area used to send messages for accessibility tech like screen readers + + + + + ContactActions + + + Warning + + + + + Could not successfully export conversation + + + + + Open Window + Context menu command to open the chat screen in a separate window + Ava aken + + + + Details... + Context menu command to open a window showing the selected contact's details + Detailid... + + + + Rename + Context menu command to rename the selected contact + Nimeta ümber + + + + Send File... + Context menu command to initiate a file transfer, opens a system file dialog + + + + + Export Conversation + Context menu command to initiate a chat log export, opens a system file dialog to export to + + + + + Remove + Context menu command to remove a contact from the contact list + Eemalda + + + + Contact options + Description of the items in the context menu for accessibility tech like screen readers + + + + + ContactIDField + + + <b>%1</b> is already your contact + Error message showed when user attempts to add a contact already in their contact list + <b>%1</b> on juba sinu kontaktides + + + + This ID is invalid + Error message showed when the id doesn't comply with spec https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt + + + + + You can't add yourself as a contact + Error message showed when user attempts to add themselves as a contact in their contact list + Sa ei saa lisada ennast kontaktiks + + + + Enter an ID starting with <b>ricochet:</b> + Error message showed when the provided ricochet id is invalid + Sisesta ID koos <b>ricochet:</b> + + + + Copied to clipboard + Message displayed when text is copied to the user's clipboard + Kopeeri lõikeväljale + + + + Copy + Text displayed on a button used to copy somethign to the user's clipboard + Kopeeri + + + + Copies the ricochet id to the clipboard + Text description of ricochet id copy button for accessibility tech like screen readers + + + + + ContactList + + + Online + Section header in the contact list for users which are online + Võrgus + + + + Offline + Section header in the contact list for users which are offline + Võrgust väljas + + + + Requests + Section header in the contact list for users requesting to be added to the user's contact list + Päringud + + + + Rejected + Section header in the contact list for users that have rejected the user's request to be added to their contact list + Tagasi lükatud + + + + Status for the given contacts + Description of the section header for accessibility tech like screen readers + + + + Outdated + Vananenud + + + + ContactPreferences + + Date added: + Lisatud kuupäeval: + + + Last seen: + Viimati nähtud: + + + Request: + Päring: + + + Pending connection + Ootel ühendus + + + Delivered + Tarnitud + + + Accepted + Aksepteeritud + + + Error + Viga + + + Rejected + Tagasi lükatud + + + %1 (Connected) + %1 status, e.g. "Accepted" + %1 (Ühendatud) + + + Response: + Vastus: + + + + Contact list + Description of the list of contacts for accessibility tech like screen readers + + + + + Contact ID for + Description of text box containing a contact's contact id for accessibility tech like screen readers + + + + + + Unknown user + A placeholder name for a contact whose name we do not know + + + + + Rename + Label for button which allows renaming of a contact + Muuda nime + + + + Renames this contact + Description of button which renames a contact for accessibility tech like screen readers + + + + + Remove + Label for button which removes a contact from the contact list + Eemalda + + + + Removes this contact + Description of button which removes a user from the contact list for accessibility tech like screen readers + + + + + Preferences for contact + Description of the contents of the 'Contacts' window for accessibility tech like screen readers + + + + + Contact Preferences Window + Name of the contact preferences window for accessibility tech like screen readers + + + + + A list of all your contacts, with their ricochet IDs, and options such as renaming and removing + Description of what user can do in the contact preferences window for accessibility tech like screen readers + + + + + ContactRequestDialog + + + Someone new is asking to connect to you + Descriptive text that is displayed in a popup window when a user tries to add you as a contact + Keegi uus palub sinuga ühenduda + + + + Reject + Label for button which rejects a contact request when pressed + Lükka tagasi + + + + Rejects the incoming contact request + Description of what 'Reject' button does for accessibility tech like screen readers + + + + + Accept + Label for button which accepts a contact request when pressed + Luba + + + + Accepts the incoming contact request + Description of what 'Accept' button does for accessibility tech like screen readers + + + + + ContactRequestFields + + + ID: + Label for the contact id text box in the 'add new contact' window + ID: + + + + Name: + Label for the contact nickname text box in the 'add new contact' window + Nimi: + + + + Field for the contact's nickname + Description of textbox for setting a contact's nickname for accessibility tech like screen readers + + + + + Message: + Label for the contact greeting message text box in the 'add new contact' window + Sõnum: + + + + Field for the contact's greeting message + Description of textbox for setting a new contact's initial greeting message for accessibility tech like screen readers + + + + + GeneralPreferences + + + Use a single window for conversations + Text description of an option to use one single program window for the contact list and the chats + Kasuta eraldi akent suhtlemiseks + + + Open links in default browser without prompting + Text description of an option to open URLs in default web browser when clicked + Ava lingid vaikimsi brauseriga ilma märguandeta + + + + Play audio notifications + Text description of an option to play audio notifications when contacts log in, log out, and send messages + Mängi heli märguandeid + + + + + Volume + Label for a slider used to adjust audio notification volume +---------- +Name of the slider used to adjust audio notification volume for accessibility tech like screen readers + Heli valjus + + + + + Language + Label for combobox where users can specify the UI language +---------- +Name of the combobox used to select UI langauge for accessibility tech like screen readers + Keel + + + + Restart Ricochet to apply changes + Taaskäivita Ricochet muudatuste jaoks + + + + What language ricochet will use + Description of what the language combox is for for accessibility tech like screen readers + + + + + LanguagePreferences + + Select Language + Keelevalik + + + Restart Ricochet to apply changes + Taaskäivita Ricochet muudatuste jaoks + + + + LanguagesModel + + + System default + Süsteemi keel + + + + Main + + + Ricochet Error + Ricocheti viga + + + + MainToolBar + + + + Add Contact + Tooltip text for the button that launches the dialog box for adding a new contact +---------- +Name of the button for adding a new contact for accessibility tech like screen readers + Lisa kontakt + + + + + Preferences + Tooltip text for the button that launches program preferences window +---------- +Name of the button for launching the preferences window for accessibility tech like screen readres + Eelistused + + + + Click to add contacts + Tooltip that displays on first launch indicating how to add a new contact + Kliki kontaktide lisamiseks + + + + Shows the add contact dialogue + Description of the 'Add Contact' button for accessibility tech like screen readers + + + + + Shows the preferences dialogue + Description of the 'Preferences' button for accessibility tech like screen readers + + + + + MainWindow + + + Version Seperator + + + + + Remove %1 + Eemalda %1 + + + + Do you want to permanently remove %1? + Kas sa tahad tingimata eemaldada: %1? + + + + Main Toolbar + Name of the main toolbar for accessibility tech like screen readers + + + + + Toolbar with connection status, add contact button, and preferences button + Description of the main toolbar for accessibility tech like screen readers + + + + + Contact pane + Name of the pane holding the user's contacts for accessibility tech like screen readers + + + + + MessageDelegate + + + %1 is offline + %1 nickname + %1 on võrgust väljas + + + + File transfer file name + Description of the text displaying the filename of a file transfer, used by accessibility tech like screen readres + + + + + File transfer progress + Description of progress bar displaying the file transfer progress, used by accessibility tech like screen readers + + + + + File transfer status + Description of label displaying the current status of a file transfer, used by accessibility tech like screen readers + + + + + Download + Label for file transfer 'Download' button for accessibility tech like screen readers + + + + + Download file + Description of what the file transfer 'Download' button does for accessibility tech like screen readers + + + + + Cancel or reject + Label for file transfer 'Cancel' button for accessibility tech like screen readers + + + + + Cancels or rejects a file transfer + Description of what the file transfer 'Cancel' button does for accessibility tech like screen readers + + + + Copy ID + Text for context menu command to copy a ricochet contact id to clipboard + Kopeeri ID + + + Copy Link + Text for context menu command to copy a url to the clipboard + Kopeeri link + + + Open with Browser + Text for context menu command to open a url in a web browser + Ava koos veebilehitsejaga + + + Add as Contact + Lisa kontaktina + + + + Copy Message + Text for context menu command to copy an entire message to clipboard + Kopeeri sõnum + + + + Copy Selection + Text for context menu command to copy selected text to clipboard + Kopeeri valik + + + + MessageDialogWrapper + + + Remove %1 + A command to remove a contact from the contact list, %1 is the contact's nickname + Eemalda %1 + + + + Do you want to permanently remove %1? + Confirmation of contact removal, %1 is the contact's nickname + Kas sa tahad tingimata eemaldada %1? + + + + This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. + Message describing what will happen when a contact is removed from you contact list + See kontakt ei saa sulle sõnumeid saata ja on märgitud eemaldamiseks. Kontakt saab saata sulle uue päringu ühendamiseks. + + + + NetworkSetupWizard + + + This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. + A label with directions for when to use the 'Connect' button + Selle arvuti interneti ühendus on vaba takistustest. Tahaksin ühenduda Tor võrku. + + + + Connect + Label for button to connect to the Tor network + Ühendus + + + + This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. + A label with directions for when to use the 'Configure' button + Selle arvuti interneti ühendus on senseeritud, filtreeritud või proksi all. Vajan seadistamaks võrguühendust. + + + + Configure + Label for button to configure the Tor daemon beore connecting to the Tor network + Seadistus + + + + OfflineStateItem + + + Configure + Button label + Seadistamine + + + + Details + Button label + Detailid + + + + Connection failed + Label displayed when connecting to the Tor network fails + Ühenduse viga + + + + Connecting… + Label displayed when in process of connecting, … is ellipsis + Ühendumine... + + + + OpenBrowserDialog + + <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. + Label displayed when user clicks on a link + <b>Hoiatus!</b> Lingi avamine sinu veebilehitsejaga võib vähendada sinu turvalisust ja anonüümsust.<br><br>Sa võid hoopis<a href='.'>kopeerida lõikeväljale</a> . + + + Don't ask again for links from %1 + Ära küsi enam linke %1 + + + Don't ask again for any links (not recommended!) + Checkbox option text for when user clicks on a link + Ära küsi rohkem mitte ühegi linkide kohta (ei ole soovitatud!) + + + Open Browser + Label on button to open link in a web browser + Ava Brauser + + + Cancel + Label on cancel button + Tühista + + + + PreferencesDialog + + + Ricochet Preferences + Ricochet seadistused + + + + Menu Tabs + Name of the tab bar for accessibility tech like screen readers + + + + + General + Title of the general settings tab + Üldine + + + Language + Keel + + + + Contacts + Title of the contacts list tab + Kontaktid + + + + Tor + Title of the tor tab, contains tor settings and logs + Tor + + + + About + Title of the about tab, contains license information and ricochet version + Lähemalt + + + + QCocoaMenuItem + + + Preference + Eelistused + + + + StartupStatusPage + + + The Tor process was not started successfully. This is most likely an installation or system error. + Error status string displayed when tor daemon does not launch successfully + Tor protsesside käivitamine ei ole õnnestunud. See on tavaliselt paigaldusel või süsteemiveal. + + + + Quit + Button title to quit/terminate the program + Lahku + + + + TorBootstrapStatus + + + Connecting to the Tor network… + Text status displayed when tor daemon is connecting, … is ellipsis + Ühendumine Tor võrku... + + + + Connection status + Description of the connection status text, used by accessibility tech like screen readers + + + + + Connection progress + Description of the bootstrap progress bar, used by accessibility tech like screen readers + + + + + + Back + Label for button which moves us back to previous screen + Tagasi + + + + Hide Details + Label for button which hides the tor logs + + + + + Show Details + Label for button which shows the tor logs + + + + + Hides the tor progress log + Description of hide details button, used by accessibility tech like screen readers + + + + + Shows the tor progress log + Description of show details button, used by accessibility tech like screen readers + + + + Hide details + Peida detailid + + + Show details + Näita detaile + + + + Done + Label for button which closes the tor connection window + Tehtud + + + + TorConfigurationPage + + + Does this computer need a proxy to access the internet? + Kas see arvuti vajab interneti ühendamiseks proksit? + + + + Proxy type: + Proksi tüüp: + + + + + None + Ei ole + + + + If you need a proxy to access the internet, select one from this list. + Description used by accessibility tech, such as screen readers + + + + + Address: + Label indicating the textbox to place a proxy IP or URL + Aadress: + + + + IP address or hostname + Placeholder text of text box expecting an IP or URL for proxy + IP aadress või hostinimi + + + + Enter the IP address or hostname of the proxy you wish to connect to + Description of what to enter into the IP textbox, used by accessibility tech such as screen readers + + + + + Port: + Label indicating the textbox to place a proxy port + Port: + + + + Port + Name of the port label, used by accessibility tech such as screen readers + + + + + Enter the port of the proxy you wish to connect to + Description of what to enter into the Port textbox, used by accessibility tech such as screen readers + + + + + Username: + Label indicating the textbox to place the proxy username + Kasutajanimi: + + + + + Optional + Textbox placeholder text indicating the field is not required + Valikuline + + + + Username + Name of the username label, used by accessibility tech such as screen readers + + + + + If required, enter the username for the proxy you wish to connect to + Description to enter into the Username textbox, used by accessibility tech such as screen readers + + + + + Password: + Label indicating the textbox to place the proxy password + Parool: + + + + Password + Name of the password label, used by accessibility tech such as screen readers + + + + + If required, enter the password for the proxy you wish to connect to + Description to enter into the Password textbox, used by accessibility tech such as screen readers + + + + + Does this computer's Internet connection go through a firewall that only allows connections to certain ports? + Description for the purpose of the Allowed Ports textbox + Kas see arvuti vajab interneti ühenduseks läbi tulemüüri ainult teatuid porte? + + + + Allowed ports: + Label indicating the textbox to place the allowed ports + Lubatud pordid: + + + + Example: 80,443 + Textbox showing an example entry for the firewall allowed ports entry + Näide: 80.443 + + + + Allowed ports + Name of the allowed ports label, used by accessibility tech such as screen readers + + + + + If this computer's Internet connection is censored, you will need to obtain and use bridge relays. + Kui selle arvuti internetiühendus on senseeritud, siis vajad ja pead saama kasutada sildühendusi. + + + + + Enter one or more bridge relays (one per line): + Sisesta üks või rohkem sildühendusi (üks rea kohta): + + + + Back + Button label for going back to previous screen + Tagasi + + + + Connect + Button label for connecting to tor + Ühenda + + + + TorLogDisplay + + + Tor log + Name of the text field containg the tor logs, used by accessibility tech such as screen readers + + + + + TorPreferences + + + Running: + Display label that beside it indicates whether tor is running + Jooksutab: + + + + Running + + + + + Whether tor is running + Description of the value of this label, used by acccessibility tech like screen readers + + + + + + Yes + Jah + + + + + No + Ei + + + External + Väline + + + + Control connected: + Display label that beside it indicates whether ricochet is connected to tor's control port + Kontrollitud ühendus: + + + + Control connected + + + + + Whether tor control connected + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Circuits established: + Ringühendused kehtestatud: + + + + Circuits established + + + + + Whether circuits established + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Hidden service: + Peidetud teenus: + + + + Hidden service + + + + + Online + Võrgus + + + + Offline + Võrgust väljas + + + + Whether a hidden service is up or not + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Version: + Versioon: + + + + Version + + + + + Error: <b>%1</b> + %1 is error message + Viga: <b>%1</b> + + + + Configure + Seadistus + + + + TorStateWidget + + + + Connection failed + Ühenduse viga + + + + + Connecting… + … is ellipsis + Ühendumine... + + + + Connecting… (%1%) + %1 is progress percentage, e.g. 100 + Ühendamine...(%1%) + + + + Online + Võrgus + + + + Connected + Ühendatud + + + + Tor status + + + + + shims::ConversationModel + + + Pending + + + + + Accepted + Aksepteeritud + + + + Rejected + Tagasi lükatud + + + + Cancelled + + + + + Complete + + + + + Unkown Failure + + + + + Bad File Hash + + + + + Network Error + + + + + File System Error + + + + + Invalid + + + + + + Open File + + + + + + + + me + + + + + Save File + + + + diff --git a/src/ricochet-refresh/translation/ricochet_fi.ts b/src/ricochet-refresh/translation/ricochet_fi.ts new file mode 100644 index 00000000..95ef17fb --- /dev/null +++ b/src/ricochet-refresh/translation/ricochet_fi.ts @@ -0,0 +1,1257 @@ + + + + + AboutPreferences + + Ricochet %1 + %1 version, e.g. 1.0.0 + Ricochet %1 + + + + Ricochet-Refresh %1 + %1 version, e.g. 1.0.0 + + + + + Ricochet Refresh version %1 + provides a readable interpretation of the Ricochet-Refresh version number for accessibility tech like screen readers + + + + + The license of Ricochet Refresh and its dependencies + + + + + License + + + + + About + Tietoa + + + + About page, contains license and version information + summary of the window's contents for accessibility tech like screen readers + + + + + Current Ricochet-Refresh version + + + + + AddContactDialog + + + Share your Ricochet ID to allow connection requests + tells the user the purpose of their Ricochet ID, which is basically a username + Jaa oma ID kontakteillesi, että he voivat lähettää yhteyspyynnön + + + + Cancel + label for button which dismisses a dialog + Peruuta + + + + Closes the contact add window + description for 'Cancel' button accessibility tech like screen readers + + + + + Add + button label to finish adding a contact/friend + Lisää + + + + Adds the contact to your contact list + description for 'Add' button for accessibility tech like screen readres + + + + + ChatPage + + + Message area + label for accessibility tech like screen readers + + + + + Write the message to be sent here. Press enter to send + description of the text area used to send messages for accessibility tech like screen readers + + + + + ContactActions + + + Warning + + + + + Could not successfully export conversation + + + + + Open Window + Context menu command to open the chat screen in a separate window + Avaa ikkuna + + + + Details... + Context menu command to open a window showing the selected contact's details + Tiedot... + + + + Rename + Context menu command to rename the selected contact + Nimeä uudelleen + + + + Send File... + Context menu command to initiate a file transfer, opens a system file dialog + + + + + Export Conversation + Context menu command to initiate a chat log export, opens a system file dialog to export to + + + + + Remove + Context menu command to remove a contact from the contact list + Poista + + + + Contact options + Description of the items in the context menu for accessibility tech like screen readers + + + + + ContactIDField + + + <b>%1</b> is already your contact + Error message showed when user attempts to add a contact already in their contact list + <b>%1</b> on jo kontaktisi + + + + This ID is invalid + Error message showed when the id doesn't comply with spec https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt + + + + + You can't add yourself as a contact + Error message showed when user attempts to add themselves as a contact in their contact list + Et voi lisätä itseäsi kontaktiksi + + + + Enter an ID starting with <b>ricochet:</b> + Error message showed when the provided ricochet id is invalid + Anna ID joka alkaa <b>ricochet:</b> + + + + Copied to clipboard + Message displayed when text is copied to the user's clipboard + Kopioitu leikepöydälle + + + + Copy + Text displayed on a button used to copy somethign to the user's clipboard + Kopioi + + + + Copies the ricochet id to the clipboard + Text description of ricochet id copy button for accessibility tech like screen readers + + + + + ContactList + + + Online + Section header in the contact list for users which are online + Linjoilla + + + + Offline + Section header in the contact list for users which are offline + Ei linjoilla + + + + Requests + Section header in the contact list for users requesting to be added to the user's contact list + Pyynnöt + + + + Rejected + Section header in the contact list for users that have rejected the user's request to be added to their contact list + Torjuttu + + + + Status for the given contacts + Description of the section header for accessibility tech like screen readers + + + + Outdated + Vanhentunut + + + + ContactPreferences + + Date added: + Lisätty: + + + Last seen: + Nähty viimeksi: + + + Request: + Pyyntö: + + + Pending connection + Odotetaan yhteyttä + + + Delivered + Toimitettu + + + Accepted + Hyväksytty + + + Error + Virhe + + + Rejected + Torjuttu + + + %1 (Connected) + %1 status, e.g. "Accepted" + %1 (Yhdistetty) + + + Response: + Vastaus: + + + + Contact list + Description of the list of contacts for accessibility tech like screen readers + + + + + Contact ID for + Description of text box containing a contact's contact id for accessibility tech like screen readers + + + + + + Unknown user + A placeholder name for a contact whose name we do not know + + + + + Rename + Label for button which allows renaming of a contact + Nimeä uudelleen + + + + Renames this contact + Description of button which renames a contact for accessibility tech like screen readers + + + + + Remove + Label for button which removes a contact from the contact list + Poista + + + + Removes this contact + Description of button which removes a user from the contact list for accessibility tech like screen readers + + + + + Preferences for contact + Description of the contents of the 'Contacts' window for accessibility tech like screen readers + + + + + Contact Preferences Window + Name of the contact preferences window for accessibility tech like screen readers + + + + + A list of all your contacts, with their ricochet IDs, and options such as renaming and removing + Description of what user can do in the contact preferences window for accessibility tech like screen readers + + + + + ContactRequestDialog + + + Someone new is asking to connect to you + Descriptive text that is displayed in a popup window when a user tries to add you as a contact + Sinulle on uusi yhteyspyyntö + + + + Reject + Label for button which rejects a contact request when pressed + Torju + + + + Rejects the incoming contact request + Description of what 'Reject' button does for accessibility tech like screen readers + + + + + Accept + Label for button which accepts a contact request when pressed + Hyväksy + + + + Accepts the incoming contact request + Description of what 'Accept' button does for accessibility tech like screen readers + + + + + ContactRequestFields + + + ID: + Label for the contact id text box in the 'add new contact' window + Saajan ID: + + + + Name: + Label for the contact nickname text box in the 'add new contact' window + Nimi: + + + + Field for the contact's nickname + Description of textbox for setting a contact's nickname for accessibility tech like screen readers + + + + + Message: + Label for the contact greeting message text box in the 'add new contact' window + Viestisi: + + + + Field for the contact's greeting message + Description of textbox for setting a new contact's initial greeting message for accessibility tech like screen readers + + + + + GeneralPreferences + + + Use a single window for conversations + Text description of an option to use one single program window for the contact list and the chats + Käytä yhtä ikkunaa keskusteluille + + + Open links in default browser without prompting + Text description of an option to open URLs in default web browser when clicked + Avaa linkit oletusselaimella ilman vahvistusta + + + + Play audio notifications + Text description of an option to play audio notifications when contacts log in, log out, and send messages + Soita huomioäänimerkkejä + + + + + Volume + Label for a slider used to adjust audio notification volume +---------- +Name of the slider used to adjust audio notification volume for accessibility tech like screen readers + Äänenvoimakkuus + + + + + Language + Label for combobox where users can specify the UI language +---------- +Name of the combobox used to select UI langauge for accessibility tech like screen readers + Kieli + + + + Restart Ricochet to apply changes + Käynnistä Ricochet uudelleen ottaaksesi tehdyt muutokset käyttöön + + + + What language ricochet will use + Description of what the language combox is for for accessibility tech like screen readers + + + + + LanguagePreferences + + Select Language + Valitse kieli + + + Restart Ricochet to apply changes + Käynnistä Ricochet uudelleen ottaaksesi tehdyt muutokset käyttöön + + + + LanguagesModel + + + System default + Systeemin oletus + + + + Main + + + Ricochet Error + Ricochetin virhe + + + + MainToolBar + + + + Add Contact + Tooltip text for the button that launches the dialog box for adding a new contact +---------- +Name of the button for adding a new contact for accessibility tech like screen readers + Lisää kontakti + + + + + Preferences + Tooltip text for the button that launches program preferences window +---------- +Name of the button for launching the preferences window for accessibility tech like screen readres + Asetukset + + + + Click to add contacts + Tooltip that displays on first launch indicating how to add a new contact + Klikkaa lisätäksesi kontakteja + + + + Shows the add contact dialogue + Description of the 'Add Contact' button for accessibility tech like screen readers + + + + + Shows the preferences dialogue + Description of the 'Preferences' button for accessibility tech like screen readers + + + + + MainWindow + + + Version Seperator + + + + + Remove %1 + Poista %1 + + + + Do you want to permanently remove %1? + Haluatko poistaa tämän pysyvästi: %1? + + + + Main Toolbar + Name of the main toolbar for accessibility tech like screen readers + + + + + Toolbar with connection status, add contact button, and preferences button + Description of the main toolbar for accessibility tech like screen readers + + + + + Contact pane + Name of the pane holding the user's contacts for accessibility tech like screen readers + + + + + MessageDelegate + + + %1 is offline + %1 nickname + %1 ei ole linjoilla + + + + File transfer file name + Description of the text displaying the filename of a file transfer, used by accessibility tech like screen readres + + + + + File transfer progress + Description of progress bar displaying the file transfer progress, used by accessibility tech like screen readers + + + + + File transfer status + Description of label displaying the current status of a file transfer, used by accessibility tech like screen readers + + + + + Download + Label for file transfer 'Download' button for accessibility tech like screen readers + + + + + Download file + Description of what the file transfer 'Download' button does for accessibility tech like screen readers + + + + + Cancel or reject + Label for file transfer 'Cancel' button for accessibility tech like screen readers + + + + + Cancels or rejects a file transfer + Description of what the file transfer 'Cancel' button does for accessibility tech like screen readers + + + + Copy ID + Text for context menu command to copy a ricochet contact id to clipboard + Kopioi ID + + + Copy Link + Text for context menu command to copy a url to the clipboard + Kopioi linkki + + + Open with Browser + Text for context menu command to open a url in a web browser + Avaa selaimessa + + + Add as Contact + Lisää kontaktiksi + + + + Copy Message + Text for context menu command to copy an entire message to clipboard + Kopioi viesti + + + + Copy Selection + Text for context menu command to copy selected text to clipboard + Kopioi valinta + + + + MessageDialogWrapper + + + Remove %1 + A command to remove a contact from the contact list, %1 is the contact's nickname + Poista %1 + + + + Do you want to permanently remove %1? + Confirmation of contact removal, %1 is the contact's nickname + Haluatko poistaa tämän pysyvästi: %1? + + + + This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. + Message describing what will happen when a contact is removed from you contact list + Tämä kontakti ei voi enää viestiä kanssasi ja hän saa tästä tiedon. Kontakti voi halutessaan lähettää sinulle uuden yhteyspyynnön. + + + + NetworkSetupWizard + + + This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. + A label with directions for when to use the 'Connect' button + Tämän tietokoneen internet-yhteydelle ei ole rajoittavia esteitä. Haluan muodostaa suoran yhteyden Tor-verkkoon. + + + + Connect + Label for button to connect to the Tor network + Yhdistä + + + + This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. + A label with directions for when to use the 'Configure' button + Tämän tietokoneen internet-yhteyttä on sensuroitu tai filtteröity tai yhteys käyttää välityspalvelimia. Minun täytyy määrittää yhteysasetukset. + + + + Configure + Label for button to configure the Tor daemon beore connecting to the Tor network + Määritä + + + + OfflineStateItem + + + Configure + Button label + Määritä + + + + Details + Button label + Tiedot + + + + Connection failed + Label displayed when connecting to the Tor network fails + Yhteys epäonnistui + + + + Connecting… + Label displayed when in process of connecting, … is ellipsis + Yhdistetään... + + + + OpenBrowserDialog + + <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. + Label displayed when user clicks on a link + <b>Varoitus!</b> Linkkien avaaminen oletusselaimessasi voi vaarantaa tietoturvaasi ja yksityisyyttäsi.<br><br>Voit <a href='.'>kopioida linkin leikepöydälle</a> tämän sijaan. + + + Don't ask again for links from %1 + Älä kysy uudestaan linkeistä joiden lähteenä on %1 + + + Don't ask again for any links (not recommended!) + Checkbox option text for when user clicks on a link + Älä kysy uudestaan mistään linkeistä (Ei suositella!) + + + Open Browser + Label on button to open link in a web browser + Avaa selain + + + Cancel + Label on cancel button + Peruuta + + + + PreferencesDialog + + + Ricochet Preferences + Ricochet asetukset + + + + Menu Tabs + Name of the tab bar for accessibility tech like screen readers + + + + + General + Title of the general settings tab + Yleistä + + + Language + Kieli + + + + Contacts + Title of the contacts list tab + Kontaktit + + + + Tor + Title of the tor tab, contains tor settings and logs + Tor + + + + About + Title of the about tab, contains license information and ricochet version + Tietoa + + + + QCocoaMenuItem + + + Preference + Asetus + + + + StartupStatusPage + + + The Tor process was not started successfully. This is most likely an installation or system error. + Error status string displayed when tor daemon does not launch successfully + Tor-prosessin käynnistys ei onnistunut. Tämä voi johtua asennus- tai järjestelmävirheestä. + + + + Quit + Button title to quit/terminate the program + Lopeta + + + + TorBootstrapStatus + + + Connecting to the Tor network… + Text status displayed when tor daemon is connecting, … is ellipsis + Yhdistetään Tor-verkkoon... + + + + Connection status + Description of the connection status text, used by accessibility tech like screen readers + + + + + Connection progress + Description of the bootstrap progress bar, used by accessibility tech like screen readers + + + + + + Back + Label for button which moves us back to previous screen + Palaa + + + + Hide Details + Label for button which hides the tor logs + + + + + Show Details + Label for button which shows the tor logs + + + + + Hides the tor progress log + Description of hide details button, used by accessibility tech like screen readers + + + + + Shows the tor progress log + Description of show details button, used by accessibility tech like screen readers + + + + Hide details + Piilota tiedot + + + Show details + Näytä tiedot + + + + Done + Label for button which closes the tor connection window + Valmis + + + + TorConfigurationPage + + + Does this computer need a proxy to access the internet? + Tarvitseeko tämä tietokone välityspalvelimen internet-yhteyden luomista varten? + + + + Proxy type: + Välityspalvelimen tyyppi: + + + + + None + Ei mitään + + + + If you need a proxy to access the internet, select one from this list. + Description used by accessibility tech, such as screen readers + + + + + Address: + Label indicating the textbox to place a proxy IP or URL + Osoite: + + + + IP address or hostname + Placeholder text of text box expecting an IP or URL for proxy + IP-osoite tai isäntä: + + + + Enter the IP address or hostname of the proxy you wish to connect to + Description of what to enter into the IP textbox, used by accessibility tech such as screen readers + + + + + Port: + Label indicating the textbox to place a proxy port + Portti: + + + + Port + Name of the port label, used by accessibility tech such as screen readers + + + + + Enter the port of the proxy you wish to connect to + Description of what to enter into the Port textbox, used by accessibility tech such as screen readers + + + + + Username: + Label indicating the textbox to place the proxy username + Käyttäjänimi: + + + + + Optional + Textbox placeholder text indicating the field is not required + Vaihtoehtoinen + + + + Username + Name of the username label, used by accessibility tech such as screen readers + + + + + If required, enter the username for the proxy you wish to connect to + Description to enter into the Username textbox, used by accessibility tech such as screen readers + + + + + Password: + Label indicating the textbox to place the proxy password + Salasana: + + + + Password + Name of the password label, used by accessibility tech such as screen readers + + + + + If required, enter the password for the proxy you wish to connect to + Description to enter into the Password textbox, used by accessibility tech such as screen readers + + + + + Does this computer's Internet connection go through a firewall that only allows connections to certain ports? + Description for the purpose of the Allowed Ports textbox + Suojaako tämän tietokoneen internet-yhteyttä palomuuri, joka sallii liikenteen vain ennalta määrätyistä porteista? + + + + Allowed ports: + Label indicating the textbox to place the allowed ports + Sallitut portit: + + + + Example: 80,443 + Textbox showing an example entry for the firewall allowed ports entry + Esimerkiksi: 80,443 + + + + Allowed ports + Name of the allowed ports label, used by accessibility tech such as screen readers + + + + + If this computer's Internet connection is censored, you will need to obtain and use bridge relays. + Jos tämän tietokoneen internet-yhteyttä on sensuroitu, sinun täytyy käyttää sillattuja reitittimiä + + + + + Enter one or more bridge relays (one per line): + Anna yksi tai useampi reititin (erottele rivinvaihdolla): + + + + Back + Button label for going back to previous screen + Palaa + + + + Connect + Button label for connecting to tor + Yhdistä + + + + TorLogDisplay + + + Tor log + Name of the text field containg the tor logs, used by accessibility tech such as screen readers + + + + + TorPreferences + + + Running: + Display label that beside it indicates whether tor is running + Käynnissä: + + + + Running + + + + + Whether tor is running + Description of the value of this label, used by acccessibility tech like screen readers + + + + + + Yes + Kyllä + + + + + No + Ei + + + External + Ulkoinen + + + + Control connected: + Display label that beside it indicates whether ricochet is connected to tor's control port + Ohjaus kytketty: + + + + Control connected + + + + + Whether tor control connected + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Circuits established: + Alueet perustettu: + + + + Circuits established + + + + + Whether circuits established + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Hidden service: + Piilotettu palvelu: + + + + Hidden service + + + + + Online + Kyllä - Online + + + + Offline + ei - Offline + + + + Whether a hidden service is up or not + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Version: + Versio: + + + + Version + + + + + Error: <b>%1</b> + %1 is error message + Virhe: <b>%1</b> + + + + Configure + Määritä + + + + TorStateWidget + + + + Connection failed + Yhteys epäonnistui + + + + + Connecting… + … is ellipsis + Yhdistetään... + + + + Connecting… (%1%) + %1 is progress percentage, e.g. 100 + Yhdistetään… (%1%) + + + + Online + Online + + + + Connected + Yhdistetty + + + + Tor status + + + + + shims::ConversationModel + + + Pending + + + + + Accepted + Hyväksytty + + + + Rejected + Torjuttu + + + + Cancelled + + + + + Complete + + + + + Unkown Failure + + + + + Bad File Hash + + + + + Network Error + + + + + File System Error + + + + + Invalid + + + + + + Open File + + + + + + + + me + + + + + Save File + + + + diff --git a/src/ricochet-refresh/translation/ricochet_fil_PH.ts b/src/ricochet-refresh/translation/ricochet_fil_PH.ts new file mode 100644 index 00000000..b717f385 --- /dev/null +++ b/src/ricochet-refresh/translation/ricochet_fil_PH.ts @@ -0,0 +1,1239 @@ + + + + + AboutPreferences + + Ricochet %1 + %1 version, e.g. 1.0.0 + Ricochet %1 + + + + Ricochet-Refresh %1 + %1 version, e.g. 1.0.0 + + + + + Ricochet Refresh version %1 + provides a readable interpretation of the Ricochet-Refresh version number for accessibility tech like screen readers + + + + + The license of Ricochet Refresh and its dependencies + + + + + License + + + + + About + Patungkol + + + + About page, contains license and version information + summary of the window's contents for accessibility tech like screen readers + + + + + Current Ricochet-Refresh version + + + + + AddContactDialog + + + Share your Ricochet ID to allow connection requests + tells the user the purpose of their Ricochet ID, which is basically a username + Ipamahagi ang iyong Ricochet ID para makatanggap ng mga kahilingan ng koneksyon + + + + Cancel + label for button which dismisses a dialog + Kanselahin + + + + Closes the contact add window + description for 'Cancel' button accessibility tech like screen readers + + + + + Add + button label to finish adding a contact/friend + Magdagdag + + + + Adds the contact to your contact list + description for 'Add' button for accessibility tech like screen readres + + + + + ChatPage + + + Message area + label for accessibility tech like screen readers + + + + + Write the message to be sent here. Press enter to send + description of the text area used to send messages for accessibility tech like screen readers + + + + + ContactActions + + + Warning + + + + + Could not successfully export conversation + + + + + Open Window + Context menu command to open the chat screen in a separate window + Magbukas ng Window + + + + Details... + Context menu command to open a window showing the selected contact's details + Detalye... + + + + Rename + Context menu command to rename the selected contact + Palitan ang pangalan + + + + Send File... + Context menu command to initiate a file transfer, opens a system file dialog + + + + + Export Conversation + Context menu command to initiate a chat log export, opens a system file dialog to export to + + + + + Remove + Context menu command to remove a contact from the contact list + Tanggalin + + + + Contact options + Description of the items in the context menu for accessibility tech like screen readers + + + + + ContactIDField + + + <b>%1</b> is already your contact + Error message showed when user attempts to add a contact already in their contact list + Si <b>%1</b> ay nasa iyong mga contact na. + + + + This ID is invalid + Error message showed when the id doesn't comply with spec https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt + + + + + You can't add yourself as a contact + Error message showed when user attempts to add themselves as a contact in their contact list + Hindi mo maaaring idagdag ang iyong sarili bilang isang contact + + + + Enter an ID starting with <b>ricochet:</b> + Error message showed when the provided ricochet id is invalid + Magpasok ng isang ID na nagsisimula sa <b>ricochet:</ b> + + + + Copied to clipboard + Message displayed when text is copied to the user's clipboard + Kinopya sa clipboard + + + + Copy + Text displayed on a button used to copy somethign to the user's clipboard + Kopyahin + + + + Copies the ricochet id to the clipboard + Text description of ricochet id copy button for accessibility tech like screen readers + + + + + ContactList + + + Online + Section header in the contact list for users which are online + Online + + + + Offline + Section header in the contact list for users which are offline + Offline + + + + Requests + Section header in the contact list for users requesting to be added to the user's contact list + Mga Kahilingan + + + + Rejected + Section header in the contact list for users that have rejected the user's request to be added to their contact list + Tinanggihan + + + + Status for the given contacts + Description of the section header for accessibility tech like screen readers + + + + + ContactPreferences + + Date added: + Petsa nang idinagdag: + + + Last seen: + Huling nakita: + + + Request: + Hiling: + + + Pending connection + Naghihintay na mga koneksyon + + + Delivered + Naihatid + + + Accepted + Natanggap + + + Error + Pagkakamali + + + Rejected + Tinanggihan + + + %1 (Connected) + %1 status, e.g. "Accepted" + %1 (Nakakonekta) + + + Response: + Tugon: + + + + Contact list + Description of the list of contacts for accessibility tech like screen readers + + + + + Contact ID for + Description of text box containing a contact's contact id for accessibility tech like screen readers + + + + + + Unknown user + A placeholder name for a contact whose name we do not know + + + + + Rename + Label for button which allows renaming of a contact + Palitan ang pangalan + + + + Renames this contact + Description of button which renames a contact for accessibility tech like screen readers + + + + + Remove + Label for button which removes a contact from the contact list + Tanggalin + + + + Removes this contact + Description of button which removes a user from the contact list for accessibility tech like screen readers + + + + + Preferences for contact + Description of the contents of the 'Contacts' window for accessibility tech like screen readers + + + + + Contact Preferences Window + Name of the contact preferences window for accessibility tech like screen readers + + + + + A list of all your contacts, with their ricochet IDs, and options such as renaming and removing + Description of what user can do in the contact preferences window for accessibility tech like screen readers + + + + + ContactRequestDialog + + + Someone new is asking to connect to you + Descriptive text that is displayed in a popup window when a user tries to add you as a contact + May bagong tao na nagtatanong upang kumonekta sa iyo + + + + Reject + Label for button which rejects a contact request when pressed + Tanggihan + + + + Rejects the incoming contact request + Description of what 'Reject' button does for accessibility tech like screen readers + + + + + Accept + Label for button which accepts a contact request when pressed + Tanggapin + + + + Accepts the incoming contact request + Description of what 'Accept' button does for accessibility tech like screen readers + + + + + ContactRequestFields + + + ID: + Label for the contact id text box in the 'add new contact' window + ID: + + + + Name: + Label for the contact nickname text box in the 'add new contact' window + Pangalan: + + + + Field for the contact's nickname + Description of textbox for setting a contact's nickname for accessibility tech like screen readers + + + + + Message: + Label for the contact greeting message text box in the 'add new contact' window + Mensahe: + + + + Field for the contact's greeting message + Description of textbox for setting a new contact's initial greeting message for accessibility tech like screen readers + + + + + GeneralPreferences + + + Use a single window for conversations + Text description of an option to use one single program window for the contact list and the chats + Gumamit ng solong window para sa mga pag-uusap + + + Open links in default browser without prompting + Text description of an option to open URLs in default web browser when clicked + Buksan ang mga link sa default na browser nang walang pagdikta + + + + Play audio notifications + Text description of an option to play audio notifications when contacts log in, log out, and send messages + + + + + + Volume + Label for a slider used to adjust audio notification volume +---------- +Name of the slider used to adjust audio notification volume for accessibility tech like screen readers + + + + + + Language + Label for combobox where users can specify the UI language +---------- +Name of the combobox used to select UI langauge for accessibility tech like screen readers + + + + + Restart Ricochet to apply changes + + + + + What language ricochet will use + Description of what the language combox is for for accessibility tech like screen readers + + + + + LanguagesModel + + + System default + + + + + Main + + + Ricochet Error + Pagkakamali sa Ricochet + + + + MainToolBar + + + + Add Contact + Tooltip text for the button that launches the dialog box for adding a new contact +---------- +Name of the button for adding a new contact for accessibility tech like screen readers + Magdagdag ng Contact + + + + + Preferences + Tooltip text for the button that launches program preferences window +---------- +Name of the button for launching the preferences window for accessibility tech like screen readres + Kagustuhan + + + + Click to add contacts + Tooltip that displays on first launch indicating how to add a new contact + I-click upang magdagdag ng contacts + + + + Shows the add contact dialogue + Description of the 'Add Contact' button for accessibility tech like screen readers + + + + + Shows the preferences dialogue + Description of the 'Preferences' button for accessibility tech like screen readers + + + + + MainWindow + + + Version Seperator + + + + + Remove %1 + Tanggalin si %1 + + + + Do you want to permanently remove %1? + Gusto mo bang permanenteng tanggalin si %1? + + + + Main Toolbar + Name of the main toolbar for accessibility tech like screen readers + + + + + Toolbar with connection status, add contact button, and preferences button + Description of the main toolbar for accessibility tech like screen readers + + + + + Contact pane + Name of the pane holding the user's contacts for accessibility tech like screen readers + + + + + MessageDelegate + + + %1 is offline + %1 nickname + Si %1 ay offline + + + + File transfer file name + Description of the text displaying the filename of a file transfer, used by accessibility tech like screen readres + + + + + File transfer progress + Description of progress bar displaying the file transfer progress, used by accessibility tech like screen readers + + + + + File transfer status + Description of label displaying the current status of a file transfer, used by accessibility tech like screen readers + + + + + Download + Label for file transfer 'Download' button for accessibility tech like screen readers + + + + + Download file + Description of what the file transfer 'Download' button does for accessibility tech like screen readers + + + + + Cancel or reject + Label for file transfer 'Cancel' button for accessibility tech like screen readers + + + + + Cancels or rejects a file transfer + Description of what the file transfer 'Cancel' button does for accessibility tech like screen readers + + + + Copy ID + Text for context menu command to copy a ricochet contact id to clipboard + Kopyahin ang ID + + + Copy Link + Text for context menu command to copy a url to the clipboard + Kopyahin ang Link + + + Open with Browser + Text for context menu command to open a url in a web browser + Buksan gamit ang Browser + + + Add as Contact + Idagdag bilang Contact + + + + Copy Message + Text for context menu command to copy an entire message to clipboard + + + + + Copy Selection + Text for context menu command to copy selected text to clipboard + + + + + MessageDialogWrapper + + + Remove %1 + A command to remove a contact from the contact list, %1 is the contact's nickname + Tanggalin si %1 + + + + Do you want to permanently remove %1? + Confirmation of contact removal, %1 is the contact's nickname + Gusto mo bang permanenteng tanggalin si %1? + + + + This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. + Message describing what will happen when a contact is removed from you contact list + Ang contact na ito ay hindi na magagawang magpadala ng mensahe sa iyo, siya ay aabisuhan tungkol sa pag-alis. Maaari nilang piliin na magpadala ng bagong kahilingan ng koneksyon. + + + + NetworkSetupWizard + + + This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. + A label with directions for when to use the 'Connect' button + Ang koneksyon sa Internet ng computer na ito ay walang hadlang. Gusto kong direktang kumonekta sa Tor network. + + + + Connect + Label for button to connect to the Tor network + Ikonekta + + + + This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. + A label with directions for when to use the 'Configure' button + Ang koneksyon sa Internet ng computer na ito ay censored, na-filter, o proxied. Kailangan ko i-configure ang mga setting ng network. + + + + Configure + Label for button to configure the Tor daemon beore connecting to the Tor network + I-configure + + + + OfflineStateItem + + + Configure + Button label + I-configure + + + + Details + Button label + Detalye + + + + Connection failed + Label displayed when connecting to the Tor network fails + Nabigo ang koneksyon + + + + Connecting… + Label displayed when in process of connecting, … is ellipsis + Kumukonekta... + + + + OpenBrowserDialog + + <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. + Label displayed when user clicks on a link + <b>Babala!</ b> Pagbukas ng mga link gamit ang iyong default na browser ay makakapinsala sa iyong seguridad at anonymity.<br> +<br> Maaari mong <a href='.'>kopyahin sa clipboard</a> sa halip. + + + Don't ask again for links from %1 + Huwag nang tatanungin ulit para sa mga link mula kay %1 + + + Don't ask again for any links (not recommended!) + Checkbox option text for when user clicks on a link + Huwag nang tatanungin ulit para sa anumang mga link (hindi inirerekomenda!) + + + Open Browser + Label on button to open link in a web browser + Buksan ang Browser + + + Cancel + Label on cancel button + Kanselahin + + + + PreferencesDialog + + + Ricochet Preferences + Kagustuhan sa Ricochet + + + + Menu Tabs + Name of the tab bar for accessibility tech like screen readers + + + + + General + Title of the general settings tab + Pangkalahatan + + + + Contacts + Title of the contacts list tab + Mga Contact + + + + Tor + Title of the tor tab, contains tor settings and logs + Tor + + + + About + Title of the about tab, contains license information and ricochet version + Patungkol + + + + QCocoaMenuItem + + + Preference + Kagustuhan + + + + StartupStatusPage + + + The Tor process was not started successfully. This is most likely an installation or system error. + Error status string displayed when tor daemon does not launch successfully + Ang proseso ng Tor ay hindi matagumpay na nakapagsimula. Ito ay pinaka-malamang na pagkakamali sa pag-install o pagkakamali sa sistema. + + + + Quit + Button title to quit/terminate the program + Mag-quit + + + + TorBootstrapStatus + + + Connecting to the Tor network… + Text status displayed when tor daemon is connecting, … is ellipsis + Kumokonekta sa Tor network... + + + + Connection status + Description of the connection status text, used by accessibility tech like screen readers + + + + + Connection progress + Description of the bootstrap progress bar, used by accessibility tech like screen readers + + + + + + Back + Label for button which moves us back to previous screen + Bumalik + + + + Hide Details + Label for button which hides the tor logs + + + + + Show Details + Label for button which shows the tor logs + + + + + Hides the tor progress log + Description of hide details button, used by accessibility tech like screen readers + + + + + Shows the tor progress log + Description of show details button, used by accessibility tech like screen readers + + + + Hide details + Itago ang mga detalye + + + Show details + Ipakita ang mga detalye + + + + Done + Label for button which closes the tor connection window + Tapos na + + + + TorConfigurationPage + + + Does this computer need a proxy to access the internet? + Kailangan ba ng computer na ito ng proxy upang ma-access ang internet? + + + + Proxy type: + Uri ng Proxy: + + + + + None + Wala + + + + If you need a proxy to access the internet, select one from this list. + Description used by accessibility tech, such as screen readers + + + + + Address: + Label indicating the textbox to place a proxy IP or URL + Address: + + + + IP address or hostname + Placeholder text of text box expecting an IP or URL for proxy + IP address o hostname + + + + Enter the IP address or hostname of the proxy you wish to connect to + Description of what to enter into the IP textbox, used by accessibility tech such as screen readers + + + + + Port: + Label indicating the textbox to place a proxy port + Port: + + + + Port + Name of the port label, used by accessibility tech such as screen readers + + + + + Enter the port of the proxy you wish to connect to + Description of what to enter into the Port textbox, used by accessibility tech such as screen readers + + + + + Username: + Label indicating the textbox to place the proxy username + Username: + + + + + Optional + Textbox placeholder text indicating the field is not required + Opsyonal + + + + Username + Name of the username label, used by accessibility tech such as screen readers + + + + + If required, enter the username for the proxy you wish to connect to + Description to enter into the Username textbox, used by accessibility tech such as screen readers + + + + + Password: + Label indicating the textbox to place the proxy password + Password: + + + + Password + Name of the password label, used by accessibility tech such as screen readers + + + + + If required, enter the password for the proxy you wish to connect to + Description to enter into the Password textbox, used by accessibility tech such as screen readers + + + + + Does this computer's Internet connection go through a firewall that only allows connections to certain ports? + Description for the purpose of the Allowed Ports textbox + Pumupunta ba ang koneksyon sa Internet ng computer na ito sa pamamagitan ng isang firewall na nagbibigay-daan lamang ng mga koneksyon sa ilang mga ports? + + + + Allowed ports: + Label indicating the textbox to place the allowed ports + Pinayagan na ports: + + + + Example: 80,443 + Textbox showing an example entry for the firewall allowed ports entry + Halimbawa: 80, 443 + + + + Allowed ports + Name of the allowed ports label, used by accessibility tech such as screen readers + + + + + If this computer's Internet connection is censored, you will need to obtain and use bridge relays. + Kung ang koneksyon sa Internet ng computer na ito ay censored, kakailanganin mo na makuha at gumamit ng bridge relays. + + + + + Enter one or more bridge relays (one per line): + Magpasok ng isa o higit pang mga bridge relays (isa bawat linya): + + + + Back + Button label for going back to previous screen + Bumalik + + + + Connect + Button label for connecting to tor + Kumonekta + + + + TorLogDisplay + + + Tor log + Name of the text field containg the tor logs, used by accessibility tech such as screen readers + + + + + TorPreferences + + + Running: + Display label that beside it indicates whether tor is running + Tumatakbo: + + + + Running + + + + + Whether tor is running + Description of the value of this label, used by acccessibility tech like screen readers + + + + + + Yes + Oo + + + + + No + Hindi + + + External + Panlabas + + + + Control connected: + Display label that beside it indicates whether ricochet is connected to tor's control port + Naakakonekta ang Control: + + + + Control connected + + + + + Whether tor control connected + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Circuits established: + Naitatag ang Circuits: + + + + Circuits established + + + + + Whether circuits established + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Hidden service: + Nakatagong serbisyo: + + + + Hidden service + + + + + Online + Online + + + + Offline + Offline + + + + Whether a hidden service is up or not + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Version: + Bersyon: + + + + Version + + + + + Error: <b>%1</b> + %1 is error message + Pagkakamali: <b>%1</b> + + + + Configure + I-configure + + + + TorStateWidget + + + + Connection failed + Nabigo ang koneksyon + + + + + Connecting… + … is ellipsis + Kumukonekta... + + + + Connecting… (%1%) + %1 is progress percentage, e.g. 100 + Kumukonekta… (%1%) + + + + Online + Online + + + + Connected + Konektado + + + + Tor status + + + + + shims::ConversationModel + + + Pending + + + + + Accepted + Natanggap + + + + Rejected + Tinanggihan + + + + Cancelled + + + + + Complete + + + + + Unkown Failure + + + + + Bad File Hash + + + + + Network Error + + + + + File System Error + + + + + Invalid + + + + + + Open File + + + + + + + + me + + + + + Save File + + + + diff --git a/src/ricochet-refresh/translation/ricochet_fr.ts b/src/ricochet-refresh/translation/ricochet_fr.ts new file mode 100644 index 00000000..ca07ddf3 --- /dev/null +++ b/src/ricochet-refresh/translation/ricochet_fr.ts @@ -0,0 +1,1257 @@ + + + + + AboutPreferences + + Ricochet %1 + %1 version, e.g. 1.0.0 + Ricochet %1 + + + + Ricochet-Refresh %1 + %1 version, e.g. 1.0.0 + + + + + Ricochet Refresh version %1 + provides a readable interpretation of the Ricochet-Refresh version number for accessibility tech like screen readers + + + + + The license of Ricochet Refresh and its dependencies + + + + + License + + + + + About + À propos + + + + About page, contains license and version information + summary of the window's contents for accessibility tech like screen readers + + + + + Current Ricochet-Refresh version + + + + + AddContactDialog + + + Share your Ricochet ID to allow connection requests + tells the user the purpose of their Ricochet ID, which is basically a username + Partagez votre ID Ricochet pour autoriser les demandes de connexion + + + + Cancel + label for button which dismisses a dialog + Annuler + + + + Closes the contact add window + description for 'Cancel' button accessibility tech like screen readers + + + + + Add + button label to finish adding a contact/friend + Ajouter + + + + Adds the contact to your contact list + description for 'Add' button for accessibility tech like screen readres + + + + + ChatPage + + + Message area + label for accessibility tech like screen readers + + + + + Write the message to be sent here. Press enter to send + description of the text area used to send messages for accessibility tech like screen readers + + + + + ContactActions + + + Warning + + + + + Could not successfully export conversation + + + + + Open Window + Context menu command to open the chat screen in a separate window + Ouvrir la fenêtre + + + + Details... + Context menu command to open a window showing the selected contact's details + Détails... + + + + Rename + Context menu command to rename the selected contact + Renommer + + + + Send File... + Context menu command to initiate a file transfer, opens a system file dialog + + + + + Export Conversation + Context menu command to initiate a chat log export, opens a system file dialog to export to + + + + + Remove + Context menu command to remove a contact from the contact list + Supprimer + + + + Contact options + Description of the items in the context menu for accessibility tech like screen readers + + + + + ContactIDField + + + <b>%1</b> is already your contact + Error message showed when user attempts to add a contact already in their contact list + <b>%1</b> est déjà votre contact + + + + This ID is invalid + Error message showed when the id doesn't comply with spec https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt + + + + + You can't add yourself as a contact + Error message showed when user attempts to add themselves as a contact in their contact list + Vous ne pouvez pas vous ajouter vous-même en tant que contact + + + + Enter an ID starting with <b>ricochet:</b> + Error message showed when the provided ricochet id is invalid + Entrez un ID commençant par <b>ricochet:</b> + + + + Copied to clipboard + Message displayed when text is copied to the user's clipboard + Copié dans le presse-papier + + + + Copy + Text displayed on a button used to copy somethign to the user's clipboard + Copier + + + + Copies the ricochet id to the clipboard + Text description of ricochet id copy button for accessibility tech like screen readers + + + + + ContactList + + + Online + Section header in the contact list for users which are online + Connecté + + + + Offline + Section header in the contact list for users which are offline + Déconnecté + + + + Requests + Section header in the contact list for users requesting to be added to the user's contact list + Requêtes + + + + Rejected + Section header in the contact list for users that have rejected the user's request to be added to their contact list + Rejeté + + + + Status for the given contacts + Description of the section header for accessibility tech like screen readers + + + + Outdated + Caduc + + + + ContactPreferences + + Date added: + Date d'ajout : + + + Last seen: + Dernière apparition : + + + Request: + Requête : + + + Pending connection + En attente de connexion + + + Delivered + Délivré + + + Accepted + Accepté + + + Error + Erreur + + + Rejected + Rejeté + + + %1 (Connected) + %1 status, e.g. "Accepted" + %1 (Connecté) + + + Response: + Réponse : + + + + Contact list + Description of the list of contacts for accessibility tech like screen readers + + + + + Contact ID for + Description of text box containing a contact's contact id for accessibility tech like screen readers + + + + + + Unknown user + A placeholder name for a contact whose name we do not know + + + + + Rename + Label for button which allows renaming of a contact + Renommer + + + + Renames this contact + Description of button which renames a contact for accessibility tech like screen readers + + + + + Remove + Label for button which removes a contact from the contact list + Supprimer + + + + Removes this contact + Description of button which removes a user from the contact list for accessibility tech like screen readers + + + + + Preferences for contact + Description of the contents of the 'Contacts' window for accessibility tech like screen readers + + + + + Contact Preferences Window + Name of the contact preferences window for accessibility tech like screen readers + + + + + A list of all your contacts, with their ricochet IDs, and options such as renaming and removing + Description of what user can do in the contact preferences window for accessibility tech like screen readers + + + + + ContactRequestDialog + + + Someone new is asking to connect to you + Descriptive text that is displayed in a popup window when a user tries to add you as a contact + Une nouvelle personne demande à se connecter à vous + + + + Reject + Label for button which rejects a contact request when pressed + Refuser + + + + Rejects the incoming contact request + Description of what 'Reject' button does for accessibility tech like screen readers + + + + + Accept + Label for button which accepts a contact request when pressed + Accepter + + + + Accepts the incoming contact request + Description of what 'Accept' button does for accessibility tech like screen readers + + + + + ContactRequestFields + + + ID: + Label for the contact id text box in the 'add new contact' window + ID : + + + + Name: + Label for the contact nickname text box in the 'add new contact' window + Nom : + + + + Field for the contact's nickname + Description of textbox for setting a contact's nickname for accessibility tech like screen readers + + + + + Message: + Label for the contact greeting message text box in the 'add new contact' window + Message : + + + + Field for the contact's greeting message + Description of textbox for setting a new contact's initial greeting message for accessibility tech like screen readers + + + + + GeneralPreferences + + + Use a single window for conversations + Text description of an option to use one single program window for the contact list and the chats + Utiliser une seule fenêtre pour les conversations + + + Open links in default browser without prompting + Text description of an option to open URLs in default web browser when clicked + Ouvrir les hyperliens directement dans le navigateur par défaut sans demander à chaque fois + + + + Play audio notifications + Text description of an option to play audio notifications when contacts log in, log out, and send messages + Jouer les notifications sonores + + + + + Volume + Label for a slider used to adjust audio notification volume +---------- +Name of the slider used to adjust audio notification volume for accessibility tech like screen readers + Volume + + + + + Language + Label for combobox where users can specify the UI language +---------- +Name of the combobox used to select UI langauge for accessibility tech like screen readers + Langue + + + + Restart Ricochet to apply changes + Redémarrer l'application pour applique les modifications + + + + What language ricochet will use + Description of what the language combox is for for accessibility tech like screen readers + + + + + LanguagePreferences + + Select Language + Choisir la langue + + + Restart Ricochet to apply changes + Redémarrer l'application pour applique les modifications + + + + LanguagesModel + + + System default + Par défaut du système + + + + Main + + + Ricochet Error + Erreur de Ricochet + + + + MainToolBar + + + + Add Contact + Tooltip text for the button that launches the dialog box for adding a new contact +---------- +Name of the button for adding a new contact for accessibility tech like screen readers + Ajouter un contact + + + + + Preferences + Tooltip text for the button that launches program preferences window +---------- +Name of the button for launching the preferences window for accessibility tech like screen readres + Préférences + + + + Click to add contacts + Tooltip that displays on first launch indicating how to add a new contact + Cliquez pour ajouter des contacts + + + + Shows the add contact dialogue + Description of the 'Add Contact' button for accessibility tech like screen readers + + + + + Shows the preferences dialogue + Description of the 'Preferences' button for accessibility tech like screen readers + + + + + MainWindow + + + Version Seperator + + + + + Remove %1 + Supprimer %1 + + + + Do you want to permanently remove %1? + Voulez-vous supprimer %1 définitivement ? + + + + Main Toolbar + Name of the main toolbar for accessibility tech like screen readers + + + + + Toolbar with connection status, add contact button, and preferences button + Description of the main toolbar for accessibility tech like screen readers + + + + + Contact pane + Name of the pane holding the user's contacts for accessibility tech like screen readers + + + + + MessageDelegate + + + %1 is offline + %1 nickname + %1 est hors-ligne + + + + File transfer file name + Description of the text displaying the filename of a file transfer, used by accessibility tech like screen readres + + + + + File transfer progress + Description of progress bar displaying the file transfer progress, used by accessibility tech like screen readers + + + + + File transfer status + Description of label displaying the current status of a file transfer, used by accessibility tech like screen readers + + + + + Download + Label for file transfer 'Download' button for accessibility tech like screen readers + + + + + Download file + Description of what the file transfer 'Download' button does for accessibility tech like screen readers + + + + + Cancel or reject + Label for file transfer 'Cancel' button for accessibility tech like screen readers + + + + + Cancels or rejects a file transfer + Description of what the file transfer 'Cancel' button does for accessibility tech like screen readers + + + + Copy ID + Text for context menu command to copy a ricochet contact id to clipboard + Copier ID + + + Copy Link + Text for context menu command to copy a url to the clipboard + Copier l'hyperlien + + + Open with Browser + Text for context menu command to open a url in a web browser + Ouvrir avec le navigateur + + + Add as Contact + Ajouter comme contact + + + + Copy Message + Text for context menu command to copy an entire message to clipboard + Copier le message + + + + Copy Selection + Text for context menu command to copy selected text to clipboard + Copier la sélection + + + + MessageDialogWrapper + + + Remove %1 + A command to remove a contact from the contact list, %1 is the contact's nickname + Supprimer %1 + + + + Do you want to permanently remove %1? + Confirmation of contact removal, %1 is the contact's nickname + Voulez-vous supprimer %1 définitivement ? + + + + This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. + Message describing what will happen when a contact is removed from you contact list + Ce contact ne sera plus en mesure de vous envoyer des messages et sera informé du retrait. Il pourra toutefois vous envoyer une nouvelle demande de connexion. + + + + NetworkSetupWizard + + + This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. + A label with directions for when to use the 'Connect' button + La connexion internet de cet ordinateur est libre d'obstacles. Je voudrais me connecter directement au réseau Tor. + + + + Connect + Label for button to connect to the Tor network + Connexion + + + + This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. + A label with directions for when to use the 'Configure' button + La connexion internet de cet ordinateur est censurée, filtrée, ou derrière un proxy. J'ai besoin de configurer les paramètres réseau. + + + + Configure + Label for button to configure the Tor daemon beore connecting to the Tor network + Configurer + + + + OfflineStateItem + + + Configure + Button label + Configurer + + + + Details + Button label + Détails + + + + Connection failed + Label displayed when connecting to the Tor network fails + Connexion échouée + + + + Connecting… + Label displayed when in process of connecting, … is ellipsis + Connexion... + + + + OpenBrowserDialog + + <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. + Label displayed when user clicks on a link + <b>Attention !</b> Ouvrir des hyperliens avec votre navigateur par défaut pourrait nuire à votre sécurité et votre anonymat.<br><br>Vous pouvez <a href='.'>copier dans le presse-papier</a> à la place. + + + Don't ask again for links from %1 + Ne plus demander pour des hyperliens provenant de %1 + + + Don't ask again for any links (not recommended!) + Checkbox option text for when user clicks on a link + Ne plus demander à nouveau pour tous les hyperliens (non recommandé) + + + Open Browser + Label on button to open link in a web browser + Ouvrir le navigateur + + + Cancel + Label on cancel button + Annuler + + + + PreferencesDialog + + + Ricochet Preferences + Préférences de Ricochet + + + + Menu Tabs + Name of the tab bar for accessibility tech like screen readers + + + + + General + Title of the general settings tab + Général + + + Language + Langue + + + + Contacts + Title of the contacts list tab + Contacts + + + + Tor + Title of the tor tab, contains tor settings and logs + Tor + + + + About + Title of the about tab, contains license information and ricochet version + À propos + + + + QCocoaMenuItem + + + Preference + Préférences + + + + StartupStatusPage + + + The Tor process was not started successfully. This is most likely an installation or system error. + Error status string displayed when tor daemon does not launch successfully + Le processus Tor n'a pas démarré avec succès. C'est probablement dû à une erreur d'installation ou du système. + + + + Quit + Button title to quit/terminate the program + Quitter + + + + TorBootstrapStatus + + + Connecting to the Tor network… + Text status displayed when tor daemon is connecting, … is ellipsis + Connexion au réseau Tor... + + + + Connection status + Description of the connection status text, used by accessibility tech like screen readers + + + + + Connection progress + Description of the bootstrap progress bar, used by accessibility tech like screen readers + + + + + + Back + Label for button which moves us back to previous screen + Retour + + + + Hide Details + Label for button which hides the tor logs + + + + + Show Details + Label for button which shows the tor logs + + + + + Hides the tor progress log + Description of hide details button, used by accessibility tech like screen readers + + + + + Shows the tor progress log + Description of show details button, used by accessibility tech like screen readers + + + + Hide details + Cacher les détails + + + Show details + Afficher les détails + + + + Done + Label for button which closes the tor connection window + Terminé + + + + TorConfigurationPage + + + Does this computer need a proxy to access the internet? + Est-ce que cet ordinateur à besoin d'un proxy pour accéder à Internet ? + + + + Proxy type: + Type de proxy : + + + + + None + Aucun + + + + If you need a proxy to access the internet, select one from this list. + Description used by accessibility tech, such as screen readers + + + + + Address: + Label indicating the textbox to place a proxy IP or URL + Adresse : + + + + IP address or hostname + Placeholder text of text box expecting an IP or URL for proxy + Adresse IP ou nom d'hôte + + + + Enter the IP address or hostname of the proxy you wish to connect to + Description of what to enter into the IP textbox, used by accessibility tech such as screen readers + + + + + Port: + Label indicating the textbox to place a proxy port + Port : + + + + Port + Name of the port label, used by accessibility tech such as screen readers + + + + + Enter the port of the proxy you wish to connect to + Description of what to enter into the Port textbox, used by accessibility tech such as screen readers + + + + + Username: + Label indicating the textbox to place the proxy username + Nom d'utilisateur : + + + + + Optional + Textbox placeholder text indicating the field is not required + En option + + + + Username + Name of the username label, used by accessibility tech such as screen readers + + + + + If required, enter the username for the proxy you wish to connect to + Description to enter into the Username textbox, used by accessibility tech such as screen readers + + + + + Password: + Label indicating the textbox to place the proxy password + Mot de passe : + + + + Password + Name of the password label, used by accessibility tech such as screen readers + + + + + If required, enter the password for the proxy you wish to connect to + Description to enter into the Password textbox, used by accessibility tech such as screen readers + + + + + Does this computer's Internet connection go through a firewall that only allows connections to certain ports? + Description for the purpose of the Allowed Ports textbox + Est-ce que la connexion Internet de cet ordinateur passe à travers un pare-feu qui autorise uniquement les connexions à certains ports ? + + + + Allowed ports: + Label indicating the textbox to place the allowed ports + Ports autorisés : + + + + Example: 80,443 + Textbox showing an example entry for the firewall allowed ports entry + Exemple : 80,443 + + + + Allowed ports + Name of the allowed ports label, used by accessibility tech such as screen readers + + + + + If this computer's Internet connection is censored, you will need to obtain and use bridge relays. + Si la connexion Internet de cet ordinateur est censurée, vous allez devoir obtenir et utiliser un pont Tor. + + + + + Enter one or more bridge relays (one per line): + Entrez un ou plusieurs ponts Tor (un par ligne) : + + + + Back + Button label for going back to previous screen + Retour + + + + Connect + Button label for connecting to tor + Connexion + + + + TorLogDisplay + + + Tor log + Name of the text field containg the tor logs, used by accessibility tech such as screen readers + + + + + TorPreferences + + + Running: + Display label that beside it indicates whether tor is running + En cours : + + + + Running + + + + + Whether tor is running + Description of the value of this label, used by acccessibility tech like screen readers + + + + + + Yes + Oui + + + + + No + Non + + + External + Externe + + + + Control connected: + Display label that beside it indicates whether ricochet is connected to tor's control port + Contrôle connecté : + + + + Control connected + + + + + Whether tor control connected + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Circuits established: + Circuits établis : + + + + Circuits established + + + + + Whether circuits established + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Hidden service: + Service caché : + + + + Hidden service + + + + + Online + En ligne + + + + Offline + Hors-ligne + + + + Whether a hidden service is up or not + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Version: + Version : + + + + Version + + + + + Error: <b>%1</b> + %1 is error message + Erreur : <b>%1</b> + + + + Configure + Configurer + + + + TorStateWidget + + + + Connection failed + Connexion échouée + + + + + Connecting… + … is ellipsis + Connexion... + + + + Connecting… (%1%) + %1 is progress percentage, e.g. 100 + Connexion… (%1%) + + + + Online + En ligne + + + + Connected + Connecté + + + + Tor status + + + + + shims::ConversationModel + + + Pending + + + + + Accepted + Accepté + + + + Rejected + Rejeté + + + + Cancelled + + + + + Complete + + + + + Unkown Failure + + + + + Bad File Hash + + + + + Network Error + + + + + File System Error + + + + + Invalid + + + + + + Open File + + + + + + + + me + + + + + Save File + + + + diff --git a/src/ricochet-refresh/translation/ricochet_he.ts b/src/ricochet-refresh/translation/ricochet_he.ts new file mode 100644 index 00000000..8bcd491d --- /dev/null +++ b/src/ricochet-refresh/translation/ricochet_he.ts @@ -0,0 +1,1257 @@ + + + + + AboutPreferences + + Ricochet %1 + %1 version, e.g. 1.0.0 + Ricochet %1 + + + + Ricochet-Refresh %1 + %1 version, e.g. 1.0.0 + + + + + Ricochet Refresh version %1 + provides a readable interpretation of the Ricochet-Refresh version number for accessibility tech like screen readers + + + + + The license of Ricochet Refresh and its dependencies + + + + + License + + + + + About + אודות + + + + About page, contains license and version information + summary of the window's contents for accessibility tech like screen readers + + + + + Current Ricochet-Refresh version + + + + + AddContactDialog + + + Share your Ricochet ID to allow connection requests + tells the user the purpose of their Ricochet ID, which is basically a username + שתף את ה Ricochet ID שלך על מנת לאפשר חיבורים חדשים + + + + Cancel + label for button which dismisses a dialog + בטל + + + + Closes the contact add window + description for 'Cancel' button accessibility tech like screen readers + + + + + Add + button label to finish adding a contact/friend + הוסיף + + + + Adds the contact to your contact list + description for 'Add' button for accessibility tech like screen readres + + + + + ChatPage + + + Message area + label for accessibility tech like screen readers + + + + + Write the message to be sent here. Press enter to send + description of the text area used to send messages for accessibility tech like screen readers + + + + + ContactActions + + + Warning + + + + + Could not successfully export conversation + + + + + Open Window + Context menu command to open the chat screen in a separate window + פתח חלון + + + + Details... + Context menu command to open a window showing the selected contact's details + פרטים נוספים... + + + + Rename + Context menu command to rename the selected contact + שנה שם + + + + Send File... + Context menu command to initiate a file transfer, opens a system file dialog + + + + + Export Conversation + Context menu command to initiate a chat log export, opens a system file dialog to export to + + + + + Remove + Context menu command to remove a contact from the contact list + מחק + + + + Contact options + Description of the items in the context menu for accessibility tech like screen readers + + + + + ContactIDField + + + <b>%1</b> is already your contact + Error message showed when user attempts to add a contact already in their contact list + <b>%1</b> הינו איש קשר קיים + + + + This ID is invalid + Error message showed when the id doesn't comply with spec https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt + + + + + You can't add yourself as a contact + Error message showed when user attempts to add themselves as a contact in their contact list + אי אפשר להוסיף עצמך לאנשי קשר + + + + Enter an ID starting with <b>ricochet:</b> + Error message showed when the provided ricochet id is invalid + הכנס שם משתמש אשר מתחיל ב <b>ricochet:</b> + + + + Copied to clipboard + Message displayed when text is copied to the user's clipboard + הועתק ללוח + + + + Copy + Text displayed on a button used to copy somethign to the user's clipboard + העתק + + + + Copies the ricochet id to the clipboard + Text description of ricochet id copy button for accessibility tech like screen readers + + + + + ContactList + + + Online + Section header in the contact list for users which are online + מחובר + + + + Offline + Section header in the contact list for users which are offline + מנותק + + + + Requests + Section header in the contact list for users requesting to be added to the user's contact list + בקשות + + + + Rejected + Section header in the contact list for users that have rejected the user's request to be added to their contact list + קשרים שנדחו + + + + Status for the given contacts + Description of the section header for accessibility tech like screen readers + + + + Outdated + אינו בתוקף + + + + ContactPreferences + + Date added: + הוסף בתאריך + + + Last seen: + נראה לאחרונה + + + Request: + בקשה: + + + Pending connection + חיבורים ממתינים לאישור + + + Delivered + נשלח + + + Accepted + התקבל + + + Error + שגיאה + + + Rejected + קשרים שנדחו + + + %1 (Connected) + %1 status, e.g. "Accepted" + %1 (מחובר) + + + Response: + תגובה: + + + + Contact list + Description of the list of contacts for accessibility tech like screen readers + + + + + Contact ID for + Description of text box containing a contact's contact id for accessibility tech like screen readers + + + + + + Unknown user + A placeholder name for a contact whose name we do not know + + + + + Rename + Label for button which allows renaming of a contact + שנה שם + + + + Renames this contact + Description of button which renames a contact for accessibility tech like screen readers + + + + + Remove + Label for button which removes a contact from the contact list + הסר + + + + Removes this contact + Description of button which removes a user from the contact list for accessibility tech like screen readers + + + + + Preferences for contact + Description of the contents of the 'Contacts' window for accessibility tech like screen readers + + + + + Contact Preferences Window + Name of the contact preferences window for accessibility tech like screen readers + + + + + A list of all your contacts, with their ricochet IDs, and options such as renaming and removing + Description of what user can do in the contact preferences window for accessibility tech like screen readers + + + + + ContactRequestDialog + + + Someone new is asking to connect to you + Descriptive text that is displayed in a popup window when a user tries to add you as a contact + התקבלה בקשת תקשורת חדשה + + + + Reject + Label for button which rejects a contact request when pressed + דחיה + + + + Rejects the incoming contact request + Description of what 'Reject' button does for accessibility tech like screen readers + + + + + Accept + Label for button which accepts a contact request when pressed + אשר + + + + Accepts the incoming contact request + Description of what 'Accept' button does for accessibility tech like screen readers + + + + + ContactRequestFields + + + ID: + Label for the contact id text box in the 'add new contact' window + זהות: + + + + Name: + Label for the contact nickname text box in the 'add new contact' window + שם: + + + + Field for the contact's nickname + Description of textbox for setting a contact's nickname for accessibility tech like screen readers + + + + + Message: + Label for the contact greeting message text box in the 'add new contact' window + הודעה: + + + + Field for the contact's greeting message + Description of textbox for setting a new contact's initial greeting message for accessibility tech like screen readers + + + + + GeneralPreferences + + + Use a single window for conversations + Text description of an option to use one single program window for the contact list and the chats + השתמש בחלון בודד עבור כל השיחות + + + Open links in default browser without prompting + Text description of an option to open URLs in default web browser when clicked + פתח קישורים בדפדפן ברירת המחדל ללא בקשת אישור + + + + Play audio notifications + Text description of an option to play audio notifications when contacts log in, log out, and send messages + השמע התראה קולית + + + + + Volume + Label for a slider used to adjust audio notification volume +---------- +Name of the slider used to adjust audio notification volume for accessibility tech like screen readers + עוצמה + + + + + Language + Label for combobox where users can specify the UI language +---------- +Name of the combobox used to select UI langauge for accessibility tech like screen readers + שפה + + + + Restart Ricochet to apply changes + הפעל מחדש את האפליקציה על מנת לעדכן את ההגדרות החדשות + + + + What language ricochet will use + Description of what the language combox is for for accessibility tech like screen readers + + + + + LanguagePreferences + + Select Language + בחר שפה + + + Restart Ricochet to apply changes + הפעל מחדש את האפליקציה על מנת לעדכן את ההגדרות החדשות + + + + LanguagesModel + + + System default + ברירת מחדל המערכת + + + + Main + + + Ricochet Error + שגיאה ב Ricochet + + + + MainToolBar + + + + Add Contact + Tooltip text for the button that launches the dialog box for adding a new contact +---------- +Name of the button for adding a new contact for accessibility tech like screen readers + הוסף איש קשר + + + + + Preferences + Tooltip text for the button that launches program preferences window +---------- +Name of the button for launching the preferences window for accessibility tech like screen readres + העדפות + + + + Click to add contacts + Tooltip that displays on first launch indicating how to add a new contact + הקלק להוספת אנשי קשר + + + + Shows the add contact dialogue + Description of the 'Add Contact' button for accessibility tech like screen readers + + + + + Shows the preferences dialogue + Description of the 'Preferences' button for accessibility tech like screen readers + + + + + MainWindow + + + Version Seperator + + + + + Remove %1 + הסר את %1 + + + + Do you want to permanently remove %1? + האם אתה רוצה להסיר לצמיתות את %1 + + + + Main Toolbar + Name of the main toolbar for accessibility tech like screen readers + + + + + Toolbar with connection status, add contact button, and preferences button + Description of the main toolbar for accessibility tech like screen readers + + + + + Contact pane + Name of the pane holding the user's contacts for accessibility tech like screen readers + + + + + MessageDelegate + + + %1 is offline + %1 nickname + %1 מנותק + + + + File transfer file name + Description of the text displaying the filename of a file transfer, used by accessibility tech like screen readres + + + + + File transfer progress + Description of progress bar displaying the file transfer progress, used by accessibility tech like screen readers + + + + + File transfer status + Description of label displaying the current status of a file transfer, used by accessibility tech like screen readers + + + + + Download + Label for file transfer 'Download' button for accessibility tech like screen readers + + + + + Download file + Description of what the file transfer 'Download' button does for accessibility tech like screen readers + + + + + Cancel or reject + Label for file transfer 'Cancel' button for accessibility tech like screen readers + + + + + Cancels or rejects a file transfer + Description of what the file transfer 'Cancel' button does for accessibility tech like screen readers + + + + Copy ID + Text for context menu command to copy a ricochet contact id to clipboard + העתק זהות + + + Copy Link + Text for context menu command to copy a url to the clipboard + העתק לינק + + + Open with Browser + Text for context menu command to open a url in a web browser + פתח בדפדפן + + + Add as Contact + הוסיף כאיש קשר + + + + Copy Message + Text for context menu command to copy an entire message to clipboard + העתק הודעה + + + + Copy Selection + Text for context menu command to copy selected text to clipboard + העתק בחירה + + + + MessageDialogWrapper + + + Remove %1 + A command to remove a contact from the contact list, %1 is the contact's nickname + הסר את %1 + + + + Do you want to permanently remove %1? + Confirmation of contact removal, %1 is the contact's nickname + האם אתה רוצה להסיר לצמיתות את %1 + + + + This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. + Message describing what will happen when a contact is removed from you contact list + איש הקשר לא יהיה מסוגל לשלוח לך הודעות, הצד השני יקבל התראה על הסרת הקישור ביניכם. הם יוכלו לשלוח בקשת חיבור מחדש + + + + NetworkSetupWizard + + + This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. + A label with directions for when to use the 'Connect' button + החיבור של המחשב הזה הינו ללא הגבלה כל שהיא כגון רשת משרדית, שרתי פרוקסי, וכדומה ואני מעוניין להתחבר ישירות לרשת Tor. + + + + Connect + Label for button to connect to the Tor network + התחבר + + + + This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. + A label with directions for when to use the 'Configure' button + החיבור של מחשב זה לאינטרנט הינו מצונזר או עובר דרך פרוקסי ראשי ואני אהיה צריך להגדיר את הגדרות הרשת. + + + + Configure + Label for button to configure the Tor daemon beore connecting to the Tor network + הגדר + + + + OfflineStateItem + + + Configure + Button label + הגדר + + + + Details + Button label + פרטים + + + + Connection failed + Label displayed when connecting to the Tor network fails + התחברות נכשלה + + + + Connecting… + Label displayed when in process of connecting, … is ellipsis + מתחבר... + + + + OpenBrowserDialog + + <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. + Label displayed when user clicks on a link + <b>זהירות!</b> פתיחת קישורים עם דפדפן הברירת מחדל שלך עלולה לסכן אותך ולחשוף פרטים עלייך!<br><br>תוכל במקום <a href=".">להעתיק את הקישור</a> ללוח. + + + Don't ask again for links from %1 + אין צורך לבקש אישור פתיחה נוסף מ %1 + + + Don't ask again for any links (not recommended!) + Checkbox option text for when user clicks on a link + אין צורך לבקש אישור מאף איש קשר (לא מומלץ ומסוכן!) + + + Open Browser + Label on button to open link in a web browser + פתח דפדפן + + + Cancel + Label on cancel button + בטל + + + + PreferencesDialog + + + Ricochet Preferences + הגדרות Ricochet + + + + Menu Tabs + Name of the tab bar for accessibility tech like screen readers + + + + + General + Title of the general settings tab + כללי + + + Language + שפה + + + + Contacts + Title of the contacts list tab + אנשי קשר + + + + Tor + Title of the tor tab, contains tor settings and logs + Tor + + + + About + Title of the about tab, contains license information and ricochet version + אודות + + + + QCocoaMenuItem + + + Preference + העדפות + + + + StartupStatusPage + + + The Tor process was not started successfully. This is most likely an installation or system error. + Error status string displayed when tor daemon does not launch successfully + תהליך Tor לא הופעל כראוי. ככל הנראה זאת תקלת מערכת או התקנה בעייתית. + + + + Quit + Button title to quit/terminate the program + יציאה / התנתק + + + + TorBootstrapStatus + + + Connecting to the Tor network… + Text status displayed when tor daemon is connecting, … is ellipsis + מתחבר לרשת Tor + + + + Connection status + Description of the connection status text, used by accessibility tech like screen readers + + + + + Connection progress + Description of the bootstrap progress bar, used by accessibility tech like screen readers + + + + + + Back + Label for button which moves us back to previous screen + אחורה + + + + Hide Details + Label for button which hides the tor logs + + + + + Show Details + Label for button which shows the tor logs + + + + + Hides the tor progress log + Description of hide details button, used by accessibility tech like screen readers + + + + + Shows the tor progress log + Description of show details button, used by accessibility tech like screen readers + + + + Hide details + הסתר פרטים + + + Show details + הראה פרטים + + + + Done + Label for button which closes the tor connection window + סיום + + + + TorConfigurationPage + + + Does this computer need a proxy to access the internet? + האם מחשב זה משתמש בפרוקסי על מנת להתחבר לאינטרנט? + + + + Proxy type: + סוג פרוקסי + + + + + None + ללא + + + + If you need a proxy to access the internet, select one from this list. + Description used by accessibility tech, such as screen readers + + + + + Address: + Label indicating the textbox to place a proxy IP or URL + כתובת: + + + + IP address or hostname + Placeholder text of text box expecting an IP or URL for proxy + כתובת IP או שם מחשב מארח + + + + Enter the IP address or hostname of the proxy you wish to connect to + Description of what to enter into the IP textbox, used by accessibility tech such as screen readers + + + + + Port: + Label indicating the textbox to place a proxy port + פורט: + + + + Port + Name of the port label, used by accessibility tech such as screen readers + + + + + Enter the port of the proxy you wish to connect to + Description of what to enter into the Port textbox, used by accessibility tech such as screen readers + + + + + Username: + Label indicating the textbox to place the proxy username + שם משתמש: + + + + + Optional + Textbox placeholder text indicating the field is not required + אופציונלי + + + + Username + Name of the username label, used by accessibility tech such as screen readers + + + + + If required, enter the username for the proxy you wish to connect to + Description to enter into the Username textbox, used by accessibility tech such as screen readers + + + + + Password: + Label indicating the textbox to place the proxy password + סיסמה: + + + + Password + Name of the password label, used by accessibility tech such as screen readers + + + + + If required, enter the password for the proxy you wish to connect to + Description to enter into the Password textbox, used by accessibility tech such as screen readers + + + + + Does this computer's Internet connection go through a firewall that only allows connections to certain ports? + Description for the purpose of the Allowed Ports textbox + האם חיבור של מחשב זה עובר דרך חומת אש אשר מרשה שימוש בפורטים מסויימים? + + + + Allowed ports: + Label indicating the textbox to place the allowed ports + פורטים מורשים: + + + + Example: 80,443 + Textbox showing an example entry for the firewall allowed ports entry + דוגמה: 80,443 + + + + Allowed ports + Name of the allowed ports label, used by accessibility tech such as screen readers + + + + + If this computer's Internet connection is censored, you will need to obtain and use bridge relays. + במידה וחיבור האינטרנט של מחשב זה מצונזר, יהיה עליך להגדיר שרתי גישור חיבור + + + + + Enter one or more bridge relays (one per line): + הכנס גשר חיבור (אחד לשורה): + + + + Back + Button label for going back to previous screen + חזרה + + + + Connect + Button label for connecting to tor + התחברהתחבר + + + + TorLogDisplay + + + Tor log + Name of the text field containg the tor logs, used by accessibility tech such as screen readers + + + + + TorPreferences + + + Running: + Display label that beside it indicates whether tor is running + פעיל: + + + + Running + + + + + Whether tor is running + Description of the value of this label, used by acccessibility tech like screen readers + + + + + + Yes + כן + + + + + No + לא + + + External + חיצוני + + + + Control connected: + Display label that beside it indicates whether ricochet is connected to tor's control port + בקרה מחוברת: + + + + Control connected + + + + + Whether tor control connected + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Circuits established: + מעגלים מחוברים: + + + + Circuits established + + + + + Whether circuits established + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Hidden service: + שירות מוסתר: + + + + Hidden service + + + + + Online + מחובר + + + + Offline + מנותק + + + + Whether a hidden service is up or not + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Version: + גירסה: + + + + Version + + + + + Error: <b>%1</b> + %1 is error message + שגיאה: <b>%1</b> + + + + Configure + הגדר + + + + TorStateWidget + + + + Connection failed + התחברות נכשלה + + + + + Connecting… + … is ellipsis + מתחבר... + + + + Connecting… (%1%) + %1 is progress percentage, e.g. 100 + מתחבר... (%1) + + + + Online + מקוון + + + + Connected + מחובר + + + + Tor status + + + + + shims::ConversationModel + + + Pending + + + + + Accepted + התקבל + + + + Rejected + קשרים שנדחו + + + + Cancelled + + + + + Complete + + + + + Unkown Failure + + + + + Bad File Hash + + + + + Network Error + + + + + File System Error + + + + + Invalid + + + + + + Open File + + + + + + + + me + + + + + Save File + + + + diff --git a/src/ricochet-refresh/translation/ricochet_it.ts b/src/ricochet-refresh/translation/ricochet_it.ts new file mode 100644 index 00000000..2cff3828 --- /dev/null +++ b/src/ricochet-refresh/translation/ricochet_it.ts @@ -0,0 +1,1253 @@ + + + + + AboutPreferences + + Ricochet %1 + %1 version, e.g. 1.0.0 + Ricochet %1 + + + + Ricochet-Refresh %1 + %1 version, e.g. 1.0.0 + + + + + Ricochet Refresh version %1 + provides a readable interpretation of the Ricochet-Refresh version number for accessibility tech like screen readers + + + + + The license of Ricochet Refresh and its dependencies + + + + + License + + + + + About + Riguardo a + + + + About page, contains license and version information + summary of the window's contents for accessibility tech like screen readers + + + + + Current Ricochet-Refresh version + + + + + AddContactDialog + + + Share your Ricochet ID to allow connection requests + tells the user the purpose of their Ricochet ID, which is basically a username + Condividi il tuo Ricochet ID per permettere le richieste di connessione + + + + Cancel + label for button which dismisses a dialog + Annulla + + + + Closes the contact add window + description for 'Cancel' button accessibility tech like screen readers + + + + + Add + button label to finish adding a contact/friend + Aggiungere + + + + Adds the contact to your contact list + description for 'Add' button for accessibility tech like screen readres + + + + + ChatPage + + + Message area + label for accessibility tech like screen readers + + + + + Write the message to be sent here. Press enter to send + description of the text area used to send messages for accessibility tech like screen readers + + + + + ContactActions + + + Warning + + + + + Could not successfully export conversation + + + + + Open Window + Context menu command to open the chat screen in a separate window + Apri Finestra + + + + Details... + Context menu command to open a window showing the selected contact's details + Dettagli... + + + + Rename + Context menu command to rename the selected contact + Rinominare + + + + Send File... + Context menu command to initiate a file transfer, opens a system file dialog + + + + + Export Conversation + Context menu command to initiate a chat log export, opens a system file dialog to export to + + + + + Remove + Context menu command to remove a contact from the contact list + Rimuovere + + + + Contact options + Description of the items in the context menu for accessibility tech like screen readers + + + + + ContactIDField + + + <b>%1</b> is already your contact + Error message showed when user attempts to add a contact already in their contact list + <b>%1</b> è già un tuo contatto + + + + This ID is invalid + Error message showed when the id doesn't comply with spec https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt + + + + + You can't add yourself as a contact + Error message showed when user attempts to add themselves as a contact in their contact list + Non puoi aggiungere te stesso come contatto + + + + Enter an ID starting with <b>ricochet:</b> + Error message showed when the provided ricochet id is invalid + Inserisci un ID che inizi con <b>ricochet:</b> + + + + Copied to clipboard + Message displayed when text is copied to the user's clipboard + Copiato negli appunti + + + + Copy + Text displayed on a button used to copy somethign to the user's clipboard + Copia + + + + Copies the ricochet id to the clipboard + Text description of ricochet id copy button for accessibility tech like screen readers + + + + + ContactList + + + Online + Section header in the contact list for users which are online + Connesso + + + + Offline + Section header in the contact list for users which are offline + Disconnesso + + + + Requests + Section header in the contact list for users requesting to be added to the user's contact list + Richieste + + + + Rejected + Section header in the contact list for users that have rejected the user's request to be added to their contact list + Rifiutato + + + + Status for the given contacts + Description of the section header for accessibility tech like screen readers + + + + + ContactPreferences + + Date added: + Data di inserimento: + + + Last seen: + Ultimo accesso: + + + Request: + Richiesta: + + + Pending connection + Connessione in sospeso + + + Delivered + Consegnato + + + Accepted + Accettato + + + Error + Errore + + + Rejected + Rifiutato + + + %1 (Connected) + %1 status, e.g. "Accepted" + %1 (Collegato) + + + Response: + Risposta: + + + + Contact list + Description of the list of contacts for accessibility tech like screen readers + + + + + Contact ID for + Description of text box containing a contact's contact id for accessibility tech like screen readers + + + + + + Unknown user + A placeholder name for a contact whose name we do not know + + + + + Rename + Label for button which allows renaming of a contact + Rinomina + + + + Renames this contact + Description of button which renames a contact for accessibility tech like screen readers + + + + + Remove + Label for button which removes a contact from the contact list + Rimuovere + + + + Removes this contact + Description of button which removes a user from the contact list for accessibility tech like screen readers + + + + + Preferences for contact + Description of the contents of the 'Contacts' window for accessibility tech like screen readers + + + + + Contact Preferences Window + Name of the contact preferences window for accessibility tech like screen readers + + + + + A list of all your contacts, with their ricochet IDs, and options such as renaming and removing + Description of what user can do in the contact preferences window for accessibility tech like screen readers + + + + + ContactRequestDialog + + + Someone new is asking to connect to you + Descriptive text that is displayed in a popup window when a user tries to add you as a contact + Qualcuno di nuovo sta chiedendo di connettersi a te + + + + Reject + Label for button which rejects a contact request when pressed + Rifiuta + + + + Rejects the incoming contact request + Description of what 'Reject' button does for accessibility tech like screen readers + + + + + Accept + Label for button which accepts a contact request when pressed + Accetta + + + + Accepts the incoming contact request + Description of what 'Accept' button does for accessibility tech like screen readers + + + + + ContactRequestFields + + + ID: + Label for the contact id text box in the 'add new contact' window + ID: + + + + Name: + Label for the contact nickname text box in the 'add new contact' window + Nome: + + + + Field for the contact's nickname + Description of textbox for setting a contact's nickname for accessibility tech like screen readers + + + + + Message: + Label for the contact greeting message text box in the 'add new contact' window + Messaggio: + + + + Field for the contact's greeting message + Description of textbox for setting a new contact's initial greeting message for accessibility tech like screen readers + + + + + GeneralPreferences + + + Use a single window for conversations + Text description of an option to use one single program window for the contact list and the chats + Usa una finestra singola per le conversazioni + + + Open links in default browser without prompting + Text description of an option to open URLs in default web browser when clicked + Apri link nel browser di default senza chiedere + + + + Play audio notifications + Text description of an option to play audio notifications when contacts log in, log out, and send messages + + + + + + Volume + Label for a slider used to adjust audio notification volume +---------- +Name of the slider used to adjust audio notification volume for accessibility tech like screen readers + Volume + + + + + Language + Label for combobox where users can specify the UI language +---------- +Name of the combobox used to select UI langauge for accessibility tech like screen readers + Lingua + + + + Restart Ricochet to apply changes + Riavvia Ricochet per applicare le modifiche + + + + What language ricochet will use + Description of what the language combox is for for accessibility tech like screen readers + + + + + LanguagePreferences + + Select Language + Selezione lingua + + + Restart Ricochet to apply changes + Riavvia Ricochet per applicare le modifiche + + + + LanguagesModel + + + System default + + + + + Main + + + Ricochet Error + Errore Ricochet + + + + MainToolBar + + + + Add Contact + Tooltip text for the button that launches the dialog box for adding a new contact +---------- +Name of the button for adding a new contact for accessibility tech like screen readers + Aggiungere Contatto + + + + + Preferences + Tooltip text for the button that launches program preferences window +---------- +Name of the button for launching the preferences window for accessibility tech like screen readres + Preferenze + + + + Click to add contacts + Tooltip that displays on first launch indicating how to add a new contact + Clicca per aggiungere contatti + + + + Shows the add contact dialogue + Description of the 'Add Contact' button for accessibility tech like screen readers + + + + + Shows the preferences dialogue + Description of the 'Preferences' button for accessibility tech like screen readers + + + + + MainWindow + + + Version Seperator + + + + + Remove %1 + Rimuovere %1 + + + + Do you want to permanently remove %1? + Vuoi rimuovere perennemente %1? + + + + Main Toolbar + Name of the main toolbar for accessibility tech like screen readers + + + + + Toolbar with connection status, add contact button, and preferences button + Description of the main toolbar for accessibility tech like screen readers + + + + + Contact pane + Name of the pane holding the user's contacts for accessibility tech like screen readers + + + + + MessageDelegate + + + %1 is offline + %1 nickname + %1 è sconnesso + + + + File transfer file name + Description of the text displaying the filename of a file transfer, used by accessibility tech like screen readres + + + + + File transfer progress + Description of progress bar displaying the file transfer progress, used by accessibility tech like screen readers + + + + + File transfer status + Description of label displaying the current status of a file transfer, used by accessibility tech like screen readers + + + + + Download + Label for file transfer 'Download' button for accessibility tech like screen readers + + + + + Download file + Description of what the file transfer 'Download' button does for accessibility tech like screen readers + + + + + Cancel or reject + Label for file transfer 'Cancel' button for accessibility tech like screen readers + + + + + Cancels or rejects a file transfer + Description of what the file transfer 'Cancel' button does for accessibility tech like screen readers + + + + Copy ID + Text for context menu command to copy a ricochet contact id to clipboard + Copia ID + + + Copy Link + Text for context menu command to copy a url to the clipboard + Copia Link + + + Open with Browser + Text for context menu command to open a url in a web browser + Apri con il Browser + + + Add as Contact + Aggiungi come Contatto + + + + Copy Message + Text for context menu command to copy an entire message to clipboard + Copia messaggio + + + + Copy Selection + Text for context menu command to copy selected text to clipboard + Copia selezione + + + + MessageDialogWrapper + + + Remove %1 + A command to remove a contact from the contact list, %1 is the contact's nickname + Rimuovere %1 + + + + Do you want to permanently remove %1? + Confirmation of contact removal, %1 is the contact's nickname + Vuoi rimuovere perennemente %1? + + + + This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. + Message describing what will happen when a contact is removed from you contact list + Questo contatto non sarà più in grado di inviarti messaggi e sarà notificato di questo. Potrebbe decidere di mandare una nuova richiesta. + + + + NetworkSetupWizard + + + This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. + A label with directions for when to use the 'Connect' button + La connessione a internet di questo computer è libera da ostacoli. Vorrei connettermi direttamente alla rete Tor. + + + + Connect + Label for button to connect to the Tor network + Collegare + + + + This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. + A label with directions for when to use the 'Configure' button + La connessione a internet di questo computer è soggetta a censure, filtri o proxy. Devo configurare le impostazioni del network. + + + + Configure + Label for button to configure the Tor daemon beore connecting to the Tor network + Configurare + + + + OfflineStateItem + + + Configure + Button label + Configurare + + + + Details + Button label + Dettagli + + + + Connection failed + Label displayed when connecting to the Tor network fails + Connessione fallita + + + + Connecting… + Label displayed when in process of connecting, … is ellipsis + Collegamento... + + + + OpenBrowserDialog + + <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. + Label displayed when user clicks on a link + <b>Attenzione!</b> Aprire link con il tuo browser di default danneggerà la tua sicurezza e anonimia.<br><br>Puoi <a href='.'>copiare negli appunti</a> invece. + + + Don't ask again for links from %1 + Non chiedere ancora per link da %1 + + + Don't ask again for any links (not recommended!) + Checkbox option text for when user clicks on a link + Non chiedere più per ogni link (non raccomandato) + + + Open Browser + Label on button to open link in a web browser + Apri Browser + + + Cancel + Label on cancel button + Annulla + + + + PreferencesDialog + + + Ricochet Preferences + Preferenze Ricochet + + + + Menu Tabs + Name of the tab bar for accessibility tech like screen readers + + + + + General + Title of the general settings tab + Generali + + + Language + Lingua + + + + Contacts + Title of the contacts list tab + Contatti + + + + Tor + Title of the tor tab, contains tor settings and logs + Tor + + + + About + Title of the about tab, contains license information and ricochet version + Riguardo a + + + + QCocoaMenuItem + + + Preference + Preferenza + + + + StartupStatusPage + + + The Tor process was not started successfully. This is most likely an installation or system error. + Error status string displayed when tor daemon does not launch successfully + Tor non è stato avviato correttamente. Probabilmente si tratta di un errore di sistema o di installazione. + + + + Quit + Button title to quit/terminate the program + Esci + + + + TorBootstrapStatus + + + Connecting to the Tor network… + Text status displayed when tor daemon is connecting, … is ellipsis + Collegamento al network Tor... + + + + Connection status + Description of the connection status text, used by accessibility tech like screen readers + + + + + Connection progress + Description of the bootstrap progress bar, used by accessibility tech like screen readers + + + + + + Back + Label for button which moves us back to previous screen + Indietro + + + + Hide Details + Label for button which hides the tor logs + + + + + Show Details + Label for button which shows the tor logs + + + + + Hides the tor progress log + Description of hide details button, used by accessibility tech like screen readers + + + + + Shows the tor progress log + Description of show details button, used by accessibility tech like screen readers + + + + Hide details + Nascondi dettagli + + + Show details + Mostra dettagli + + + + Done + Label for button which closes the tor connection window + Fatto + + + + TorConfigurationPage + + + Does this computer need a proxy to access the internet? + Questo computer necessita di un proxy per accedere a internet? + + + + Proxy type: + Tipo di proxy: + + + + + None + Nessuno + + + + If you need a proxy to access the internet, select one from this list. + Description used by accessibility tech, such as screen readers + + + + + Address: + Label indicating the textbox to place a proxy IP or URL + Indirizzo: + + + + IP address or hostname + Placeholder text of text box expecting an IP or URL for proxy + Indirizzo IP o nome host + + + + Enter the IP address or hostname of the proxy you wish to connect to + Description of what to enter into the IP textbox, used by accessibility tech such as screen readers + + + + + Port: + Label indicating the textbox to place a proxy port + Porta: + + + + Port + Name of the port label, used by accessibility tech such as screen readers + + + + + Enter the port of the proxy you wish to connect to + Description of what to enter into the Port textbox, used by accessibility tech such as screen readers + + + + + Username: + Label indicating the textbox to place the proxy username + Nome utente: + + + + + Optional + Textbox placeholder text indicating the field is not required + Opzionale + + + + Username + Name of the username label, used by accessibility tech such as screen readers + + + + + If required, enter the username for the proxy you wish to connect to + Description to enter into the Username textbox, used by accessibility tech such as screen readers + + + + + Password: + Label indicating the textbox to place the proxy password + Password: + + + + Password + Name of the password label, used by accessibility tech such as screen readers + + + + + If required, enter the password for the proxy you wish to connect to + Description to enter into the Password textbox, used by accessibility tech such as screen readers + + + + + Does this computer's Internet connection go through a firewall that only allows connections to certain ports? + Description for the purpose of the Allowed Ports textbox + La connessione di questo computer passa attraverso un firewall che permette la connessione solo ad alcune porte? + + + + Allowed ports: + Label indicating the textbox to place the allowed ports + Porte permesse: + + + + Example: 80,443 + Textbox showing an example entry for the firewall allowed ports entry + Esempio: 80,443 + + + + Allowed ports + Name of the allowed ports label, used by accessibility tech such as screen readers + + + + + If this computer's Internet connection is censored, you will need to obtain and use bridge relays. + Se la connessione a internet di questo computer è censurata dovrai ottenere ed usare dei bridge relay + + + + + Enter one or more bridge relays (one per line): + Inserisci uno o più bridge relay (uno per linea): + + + + Back + Button label for going back to previous screen + Indietro + + + + Connect + Button label for connecting to tor + Collegare + + + + TorLogDisplay + + + Tor log + Name of the text field containg the tor logs, used by accessibility tech such as screen readers + + + + + TorPreferences + + + Running: + Display label that beside it indicates whether tor is running + In esecuzione: + + + + Running + + + + + Whether tor is running + Description of the value of this label, used by acccessibility tech like screen readers + + + + + + Yes + Si + + + + + No + No + + + External + Esterno + + + + Control connected: + Display label that beside it indicates whether ricochet is connected to tor's control port + Controllo connesso: + + + + Control connected + + + + + Whether tor control connected + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Circuits established: + Circuiti stabiliti: + + + + Circuits established + + + + + Whether circuits established + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Hidden service: + Hidden service: + + + + Hidden service + + + + + Online + Connesso + + + + Offline + Disconnesso + + + + Whether a hidden service is up or not + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Version: + Versione: + + + + Version + + + + + Error: <b>%1</b> + %1 is error message + Errore: <b>%1</b> + + + + Configure + Configurare + + + + TorStateWidget + + + + Connection failed + Connessione fallita + + + + + Connecting… + … is ellipsis + Collegamento... + + + + Connecting… (%1%) + %1 is progress percentage, e.g. 100 + Collegamento... (%1%) + + + + Online + Connesso + + + + Connected + Connesso + + + + Tor status + + + + + shims::ConversationModel + + + Pending + + + + + Accepted + Accettato + + + + Rejected + Rifiutato + + + + Cancelled + + + + + Complete + + + + + Unkown Failure + + + + + Bad File Hash + + + + + Network Error + + + + + File System Error + + + + + Invalid + + + + + + Open File + + + + + + + + me + + + + + Save File + + + + diff --git a/src/ricochet-refresh/translation/ricochet_it_IT.ts b/src/ricochet-refresh/translation/ricochet_it_IT.ts new file mode 100644 index 00000000..3a070190 --- /dev/null +++ b/src/ricochet-refresh/translation/ricochet_it_IT.ts @@ -0,0 +1,1257 @@ + + + + + AboutPreferences + + Ricochet %1 + %1 version, e.g. 1.0.0 + Ricochet %1 + + + + Ricochet-Refresh %1 + %1 version, e.g. 1.0.0 + + + + + Ricochet Refresh version %1 + provides a readable interpretation of the Ricochet-Refresh version number for accessibility tech like screen readers + + + + + The license of Ricochet Refresh and its dependencies + + + + + License + + + + + About + Info + + + + About page, contains license and version information + summary of the window's contents for accessibility tech like screen readers + + + + + Current Ricochet-Refresh version + + + + + AddContactDialog + + + Share your Ricochet ID to allow connection requests + tells the user the purpose of their Ricochet ID, which is basically a username + Condividi il tuo Ricochet ID per permettere agli altri utenti di inviarti richieste di connessione + + + + Cancel + label for button which dismisses a dialog + Annulla + + + + Closes the contact add window + description for 'Cancel' button accessibility tech like screen readers + + + + + Add + button label to finish adding a contact/friend + Aggiungi + + + + Adds the contact to your contact list + description for 'Add' button for accessibility tech like screen readres + + + + + ChatPage + + + Message area + label for accessibility tech like screen readers + + + + + Write the message to be sent here. Press enter to send + description of the text area used to send messages for accessibility tech like screen readers + + + + + ContactActions + + + Warning + + + + + Could not successfully export conversation + + + + + Open Window + Context menu command to open the chat screen in a separate window + Apri finestra + + + + Details... + Context menu command to open a window showing the selected contact's details + Dettagli... + + + + Rename + Context menu command to rename the selected contact + Rinomina + + + + Send File... + Context menu command to initiate a file transfer, opens a system file dialog + + + + + Export Conversation + Context menu command to initiate a chat log export, opens a system file dialog to export to + + + + + Remove + Context menu command to remove a contact from the contact list + Rimuovi + + + + Contact options + Description of the items in the context menu for accessibility tech like screen readers + + + + + ContactIDField + + + <b>%1</b> is already your contact + Error message showed when user attempts to add a contact already in their contact list + <b>%1</b> è già tra i tuoi contatti + + + + This ID is invalid + Error message showed when the id doesn't comply with spec https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt + + + + + You can't add yourself as a contact + Error message showed when user attempts to add themselves as a contact in their contact list + Non puoi aggiungere te stesso ai tuoi contatti + + + + Enter an ID starting with <b>ricochet:</b> + Error message showed when the provided ricochet id is invalid + Inserisci un ID che inizi con <b>ricochet:</b> + + + + Copied to clipboard + Message displayed when text is copied to the user's clipboard + Copiato negli appunti + + + + Copy + Text displayed on a button used to copy somethign to the user's clipboard + Copia + + + + Copies the ricochet id to the clipboard + Text description of ricochet id copy button for accessibility tech like screen readers + + + + + ContactList + + + Online + Section header in the contact list for users which are online + Connesso + + + + Offline + Section header in the contact list for users which are offline + Disconnesso + + + + Requests + Section header in the contact list for users requesting to be added to the user's contact list + Richieste + + + + Rejected + Section header in the contact list for users that have rejected the user's request to be added to their contact list + Rifiutato + + + + Status for the given contacts + Description of the section header for accessibility tech like screen readers + + + + Outdated + Non aggiornato + + + + ContactPreferences + + Date added: + Aggiunto il: + + + Last seen: + Ultimo accesso: + + + Request: + Richiesta: + + + Pending connection + Connessione in attesa + + + Delivered + Consegnato + + + Accepted + Accettato + + + Error + Errore + + + Rejected + Rifiutato + + + %1 (Connected) + %1 status, e.g. "Accepted" + %1 (In linea) + + + Response: + Risposta: + + + + Contact list + Description of the list of contacts for accessibility tech like screen readers + + + + + Contact ID for + Description of text box containing a contact's contact id for accessibility tech like screen readers + + + + + + Unknown user + A placeholder name for a contact whose name we do not know + + + + + Rename + Label for button which allows renaming of a contact + Rinomina + + + + Renames this contact + Description of button which renames a contact for accessibility tech like screen readers + + + + + Remove + Label for button which removes a contact from the contact list + Rimuovi + + + + Removes this contact + Description of button which removes a user from the contact list for accessibility tech like screen readers + + + + + Preferences for contact + Description of the contents of the 'Contacts' window for accessibility tech like screen readers + + + + + Contact Preferences Window + Name of the contact preferences window for accessibility tech like screen readers + + + + + A list of all your contacts, with their ricochet IDs, and options such as renaming and removing + Description of what user can do in the contact preferences window for accessibility tech like screen readers + + + + + ContactRequestDialog + + + Someone new is asking to connect to you + Descriptive text that is displayed in a popup window when a user tries to add you as a contact + Un nuovo utente vuole connettersi con te + + + + Reject + Label for button which rejects a contact request when pressed + Rifiuta + + + + Rejects the incoming contact request + Description of what 'Reject' button does for accessibility tech like screen readers + + + + + Accept + Label for button which accepts a contact request when pressed + Accetta + + + + Accepts the incoming contact request + Description of what 'Accept' button does for accessibility tech like screen readers + + + + + ContactRequestFields + + + ID: + Label for the contact id text box in the 'add new contact' window + ID: + + + + Name: + Label for the contact nickname text box in the 'add new contact' window + Nome: + + + + Field for the contact's nickname + Description of textbox for setting a contact's nickname for accessibility tech like screen readers + + + + + Message: + Label for the contact greeting message text box in the 'add new contact' window + Messaggio: + + + + Field for the contact's greeting message + Description of textbox for setting a new contact's initial greeting message for accessibility tech like screen readers + + + + + GeneralPreferences + + + Use a single window for conversations + Text description of an option to use one single program window for the contact list and the chats + Usa una finestra singola per le conversazioni + + + Open links in default browser without prompting + Text description of an option to open URLs in default web browser when clicked + Apri i link nel browser di default senza chiedere + + + + Play audio notifications + Text description of an option to play audio notifications when contacts log in, log out, and send messages + Riproduci suono notifiche + + + + + Volume + Label for a slider used to adjust audio notification volume +---------- +Name of the slider used to adjust audio notification volume for accessibility tech like screen readers + Volume + + + + + Language + Label for combobox where users can specify the UI language +---------- +Name of the combobox used to select UI langauge for accessibility tech like screen readers + Lingua + + + + Restart Ricochet to apply changes + Riavvia Ricochet per applicare le modifiche + + + + What language ricochet will use + Description of what the language combox is for for accessibility tech like screen readers + + + + + LanguagePreferences + + Select Language + Seleziona lingua + + + Restart Ricochet to apply changes + Riavvia Ricochet per applicare le modifiche + + + + LanguagesModel + + + System default + Impostazioni di fabbrica + + + + Main + + + Ricochet Error + Errore Ricochet + + + + MainToolBar + + + + Add Contact + Tooltip text for the button that launches the dialog box for adding a new contact +---------- +Name of the button for adding a new contact for accessibility tech like screen readers + Aggiungi contatto + + + + + Preferences + Tooltip text for the button that launches program preferences window +---------- +Name of the button for launching the preferences window for accessibility tech like screen readres + Preferenze + + + + Click to add contacts + Tooltip that displays on first launch indicating how to add a new contact + Clicca per aggiungere contatti + + + + Shows the add contact dialogue + Description of the 'Add Contact' button for accessibility tech like screen readers + + + + + Shows the preferences dialogue + Description of the 'Preferences' button for accessibility tech like screen readers + + + + + MainWindow + + + Version Seperator + + + + + Remove %1 + Rimuovi %1 + + + + Do you want to permanently remove %1? + Vuoi rimuovere definitivamente %1? + + + + Main Toolbar + Name of the main toolbar for accessibility tech like screen readers + + + + + Toolbar with connection status, add contact button, and preferences button + Description of the main toolbar for accessibility tech like screen readers + + + + + Contact pane + Name of the pane holding the user's contacts for accessibility tech like screen readers + + + + + MessageDelegate + + + %1 is offline + %1 nickname + %1 non è in linea + + + + File transfer file name + Description of the text displaying the filename of a file transfer, used by accessibility tech like screen readres + + + + + File transfer progress + Description of progress bar displaying the file transfer progress, used by accessibility tech like screen readers + + + + + File transfer status + Description of label displaying the current status of a file transfer, used by accessibility tech like screen readers + + + + + Download + Label for file transfer 'Download' button for accessibility tech like screen readers + + + + + Download file + Description of what the file transfer 'Download' button does for accessibility tech like screen readers + + + + + Cancel or reject + Label for file transfer 'Cancel' button for accessibility tech like screen readers + + + + + Cancels or rejects a file transfer + Description of what the file transfer 'Cancel' button does for accessibility tech like screen readers + + + + Copy ID + Text for context menu command to copy a ricochet contact id to clipboard + Copia ID + + + Copy Link + Text for context menu command to copy a url to the clipboard + Copia link + + + Open with Browser + Text for context menu command to open a url in a web browser + Apri nel il browser + + + Add as Contact + Aggiungi ai tuoi contatti + + + + Copy Message + Text for context menu command to copy an entire message to clipboard + Copia messaggio + + + + Copy Selection + Text for context menu command to copy selected text to clipboard + Copia selezione + + + + MessageDialogWrapper + + + Remove %1 + A command to remove a contact from the contact list, %1 is the contact's nickname + Rimuovi %1 + + + + Do you want to permanently remove %1? + Confirmation of contact removal, %1 is the contact's nickname + Vuoi rimuovere %1 definitivamente? + + + + This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. + Message describing what will happen when a contact is removed from you contact list + Questo contatto non potrà più inviarti messaggi. Riceverà un avviso per informarlo di questo. Potrebbe comunque inviarti una nuova richiesta di inserimento fra i contatti. + + + + NetworkSetupWizard + + + This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. + A label with directions for when to use the 'Connect' button + La connessione a Internet di questo computer è libera da ostacoli. Voglio connettermi direttamente alla rete Tor. + + + + Connect + Label for button to connect to the Tor network + Connetti + + + + This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. + A label with directions for when to use the 'Configure' button + La connessione a Internet di questo computer è soggetta a censure, filtri o proxy. Devo configurare le impostazioni di rete. + + + + Configure + Label for button to configure the Tor daemon beore connecting to the Tor network + Configura + + + + OfflineStateItem + + + Configure + Button label + Configura + + + + Details + Button label + Dettagli + + + + Connection failed + Label displayed when connecting to the Tor network fails + Connessione fallita + + + + Connecting… + Label displayed when in process of connecting, … is ellipsis + Connessione in corso... + + + + OpenBrowserDialog + + <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. + Label displayed when user clicks on a link + <b>Attenzione!</b> Aprire un link nel browser di default metterà a repentaglio la tua sicurezza e anonimia.<br><br>Puoi invece <a href='.'>copiarlo negli appunti</a>. + + + Don't ask again for links from %1 + Non chiedere più per i link inviati da %1 + + + Don't ask again for any links (not recommended!) + Checkbox option text for when user clicks on a link + Non chiedere più per ogni link (scelta non consigliata) + + + Open Browser + Label on button to open link in a web browser + Apri browser + + + Cancel + Label on cancel button + Annulla + + + + PreferencesDialog + + + Ricochet Preferences + Preferenze Ricochet + + + + Menu Tabs + Name of the tab bar for accessibility tech like screen readers + + + + + General + Title of the general settings tab + Generale + + + Language + Lingua + + + + Contacts + Title of the contacts list tab + Contatti + + + + Tor + Title of the tor tab, contains tor settings and logs + Tor + + + + About + Title of the about tab, contains license information and ricochet version + Info + + + + QCocoaMenuItem + + + Preference + Preferenza + + + + StartupStatusPage + + + The Tor process was not started successfully. This is most likely an installation or system error. + Error status string displayed when tor daemon does not launch successfully + Tor non si è avviato correttamente. Potrebbe trattarsi di un errore di sistema o di installazione. + + + + Quit + Button title to quit/terminate the program + Esci + + + + TorBootstrapStatus + + + Connecting to the Tor network… + Text status displayed when tor daemon is connecting, … is ellipsis + Collegamento alla rete Tor... + + + + Connection status + Description of the connection status text, used by accessibility tech like screen readers + + + + + Connection progress + Description of the bootstrap progress bar, used by accessibility tech like screen readers + + + + + + Back + Label for button which moves us back to previous screen + Indietro + + + + Hide Details + Label for button which hides the tor logs + + + + + Show Details + Label for button which shows the tor logs + + + + + Hides the tor progress log + Description of hide details button, used by accessibility tech like screen readers + + + + + Shows the tor progress log + Description of show details button, used by accessibility tech like screen readers + + + + Hide details + Nascondi dettagli + + + Show details + Mostra dettagli + + + + Done + Label for button which closes the tor connection window + Fatto + + + + TorConfigurationPage + + + Does this computer need a proxy to access the internet? + Questo computer necessita di un proxy per accedere a internet? + + + + Proxy type: + Tipo di proxy: + + + + + None + Nessuno + + + + If you need a proxy to access the internet, select one from this list. + Description used by accessibility tech, such as screen readers + + + + + Address: + Label indicating the textbox to place a proxy IP or URL + Indirizzo: + + + + IP address or hostname + Placeholder text of text box expecting an IP or URL for proxy + Indirizzo IP o nome host + + + + Enter the IP address or hostname of the proxy you wish to connect to + Description of what to enter into the IP textbox, used by accessibility tech such as screen readers + + + + + Port: + Label indicating the textbox to place a proxy port + Porta: + + + + Port + Name of the port label, used by accessibility tech such as screen readers + + + + + Enter the port of the proxy you wish to connect to + Description of what to enter into the Port textbox, used by accessibility tech such as screen readers + + + + + Username: + Label indicating the textbox to place the proxy username + Nome utente: + + + + + Optional + Textbox placeholder text indicating the field is not required + Facoltativo + + + + Username + Name of the username label, used by accessibility tech such as screen readers + + + + + If required, enter the username for the proxy you wish to connect to + Description to enter into the Username textbox, used by accessibility tech such as screen readers + + + + + Password: + Label indicating the textbox to place the proxy password + Password: + + + + Password + Name of the password label, used by accessibility tech such as screen readers + + + + + If required, enter the password for the proxy you wish to connect to + Description to enter into the Password textbox, used by accessibility tech such as screen readers + + + + + Does this computer's Internet connection go through a firewall that only allows connections to certain ports? + Description for the purpose of the Allowed Ports textbox + La connessione ad Internet di questo computer passa attraverso un firewall che consente la connessione solo su determinate porte? + + + + Allowed ports: + Label indicating the textbox to place the allowed ports + Porte consentite: + + + + Example: 80,443 + Textbox showing an example entry for the firewall allowed ports entry + Esempio: 80,443 + + + + Allowed ports + Name of the allowed ports label, used by accessibility tech such as screen readers + + + + + If this computer's Internet connection is censored, you will need to obtain and use bridge relays. + Se la connessione a Internet di questo computer è censurata dovrai ottenere ed usare dei bridge relay + + + + + Enter one or more bridge relays (one per line): + Aggiungi uno o più bridge relay (uno per linea): + + + + Back + Button label for going back to previous screen + Indietro + + + + Connect + Button label for connecting to tor + Connetti + + + + TorLogDisplay + + + Tor log + Name of the text field containg the tor logs, used by accessibility tech such as screen readers + + + + + TorPreferences + + + Running: + Display label that beside it indicates whether tor is running + In esecuzione: + + + + Running + + + + + Whether tor is running + Description of the value of this label, used by acccessibility tech like screen readers + + + + + + Yes + Si + + + + + No + No + + + External + Esterno + + + + Control connected: + Display label that beside it indicates whether ricochet is connected to tor's control port + Control connesso: + + + + Control connected + + + + + Whether tor control connected + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Circuits established: + Circuiti stabiliti: + + + + Circuits established + + + + + Whether circuits established + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Hidden service: + Hidden service: + + + + Hidden service + + + + + Online + Connesso + + + + Offline + Disconnesso + + + + Whether a hidden service is up or not + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Version: + Versione: + + + + Version + + + + + Error: <b>%1</b> + %1 is error message + Errore: <b>%1</b> + + + + Configure + Configura + + + + TorStateWidget + + + + Connection failed + Connessione fallita + + + + + Connecting… + … is ellipsis + Connessione in corso... + + + + Connecting… (%1%) + %1 is progress percentage, e.g. 100 + Connessione in corso... (%1%) + + + + Online + Connesso + + + + Connected + Connesso + + + + Tor status + + + + + shims::ConversationModel + + + Pending + + + + + Accepted + Accettato + + + + Rejected + Rifiutato + + + + Cancelled + + + + + Complete + + + + + Unkown Failure + + + + + Bad File Hash + + + + + Network Error + + + + + File System Error + + + + + Invalid + + + + + + Open File + + + + + + + + me + + + + + Save File + + + + diff --git a/src/ricochet-refresh/translation/ricochet_ja.ts b/src/ricochet-refresh/translation/ricochet_ja.ts new file mode 100644 index 00000000..49cae3ee --- /dev/null +++ b/src/ricochet-refresh/translation/ricochet_ja.ts @@ -0,0 +1,1257 @@ + + + + + AboutPreferences + + Ricochet %1 + %1 version, e.g. 1.0.0 + リコシェイ %1 + + + + Ricochet-Refresh %1 + %1 version, e.g. 1.0.0 + + + + + Ricochet Refresh version %1 + provides a readable interpretation of the Ricochet-Refresh version number for accessibility tech like screen readers + + + + + The license of Ricochet Refresh and its dependencies + + + + + License + + + + + About + 情報 + + + + About page, contains license and version information + summary of the window's contents for accessibility tech like screen readers + + + + + Current Ricochet-Refresh version + + + + + AddContactDialog + + + Share your Ricochet ID to allow connection requests + tells the user the purpose of their Ricochet ID, which is basically a username + 接続リクエストを許可するためにリコシェイ IDをシェアしてください + + + + Cancel + label for button which dismisses a dialog + 取り消す + + + + Closes the contact add window + description for 'Cancel' button accessibility tech like screen readers + + + + + Add + button label to finish adding a contact/friend + 追加する + + + + Adds the contact to your contact list + description for 'Add' button for accessibility tech like screen readres + + + + + ChatPage + + + Message area + label for accessibility tech like screen readers + + + + + Write the message to be sent here. Press enter to send + description of the text area used to send messages for accessibility tech like screen readers + + + + + ContactActions + + + Warning + + + + + Could not successfully export conversation + + + + + Open Window + Context menu command to open the chat screen in a separate window + ウィンドウを開く + + + + Details... + Context menu command to open a window showing the selected contact's details + 詳細 + + + + Rename + Context menu command to rename the selected contact + 名前を変更する + + + + Send File... + Context menu command to initiate a file transfer, opens a system file dialog + + + + + Export Conversation + Context menu command to initiate a chat log export, opens a system file dialog to export to + + + + + Remove + Context menu command to remove a contact from the contact list + 削除する + + + + Contact options + Description of the items in the context menu for accessibility tech like screen readers + + + + + ContactIDField + + + <b>%1</b> is already your contact + Error message showed when user attempts to add a contact already in their contact list + <b>%1</b> は既にあなたのコンタクトに存在します + + + + This ID is invalid + Error message showed when the id doesn't comply with spec https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt + + + + + You can't add yourself as a contact + Error message showed when user attempts to add themselves as a contact in their contact list + 自分自身をコンタクトに追加することはできません + + + + Enter an ID starting with <b>ricochet:</b> + Error message showed when the provided ricochet id is invalid + <b>リコシェイ</b> から始まるID を入力してください + + + + Copied to clipboard + Message displayed when text is copied to the user's clipboard + クリップボードにコピーしました + + + + Copy + Text displayed on a button used to copy somethign to the user's clipboard + コピー + + + + Copies the ricochet id to the clipboard + Text description of ricochet id copy button for accessibility tech like screen readers + + + + + ContactList + + + Online + Section header in the contact list for users which are online + オンライン + + + + Offline + Section header in the contact list for users which are offline + オフライン + + + + Requests + Section header in the contact list for users requesting to be added to the user's contact list + リクエスト + + + + Rejected + Section header in the contact list for users that have rejected the user's request to be added to their contact list + 拒否されました + + + + Status for the given contacts + Description of the section header for accessibility tech like screen readers + + + + Outdated + 古い + + + + ContactPreferences + + Date added: + 追加された日 + + + Last seen: + 最後に閲覧 + + + Request: + リクエスト + + + Pending connection + 接続を保留中です + + + Delivered + 送信完了 + + + Accepted + 受諾されました + + + Error + エラー + + + Rejected + 拒否されました + + + %1 (Connected) + %1 status, e.g. "Accepted" + %1(接続されました) + + + Response: + 返答 + + + + Contact list + Description of the list of contacts for accessibility tech like screen readers + + + + + Contact ID for + Description of text box containing a contact's contact id for accessibility tech like screen readers + + + + + + Unknown user + A placeholder name for a contact whose name we do not know + + + + + Rename + Label for button which allows renaming of a contact + 名前を変更する + + + + Renames this contact + Description of button which renames a contact for accessibility tech like screen readers + + + + + Remove + Label for button which removes a contact from the contact list + 削除する + + + + Removes this contact + Description of button which removes a user from the contact list for accessibility tech like screen readers + + + + + Preferences for contact + Description of the contents of the 'Contacts' window for accessibility tech like screen readers + + + + + Contact Preferences Window + Name of the contact preferences window for accessibility tech like screen readers + + + + + A list of all your contacts, with their ricochet IDs, and options such as renaming and removing + Description of what user can do in the contact preferences window for accessibility tech like screen readers + + + + + ContactRequestDialog + + + Someone new is asking to connect to you + Descriptive text that is displayed in a popup window when a user tries to add you as a contact + あなたとつながりたい人がいます + + + + Reject + Label for button which rejects a contact request when pressed + 拒否する + + + + Rejects the incoming contact request + Description of what 'Reject' button does for accessibility tech like screen readers + + + + + Accept + Label for button which accepts a contact request when pressed + 受諾する + + + + Accepts the incoming contact request + Description of what 'Accept' button does for accessibility tech like screen readers + + + + + ContactRequestFields + + + ID: + Label for the contact id text box in the 'add new contact' window + ID + + + + Name: + Label for the contact nickname text box in the 'add new contact' window + 名前 + + + + Field for the contact's nickname + Description of textbox for setting a contact's nickname for accessibility tech like screen readers + + + + + Message: + Label for the contact greeting message text box in the 'add new contact' window + メッセージ + + + + Field for the contact's greeting message + Description of textbox for setting a new contact's initial greeting message for accessibility tech like screen readers + + + + + GeneralPreferences + + + Use a single window for conversations + Text description of an option to use one single program window for the contact list and the chats + 一つのウィンドウで会話する + + + Open links in default browser without prompting + Text description of an option to open URLs in default web browser when clicked + ブラウザでウェブリンクを開く際には都度通知せず開く + + + + Play audio notifications + Text description of an option to play audio notifications when contacts log in, log out, and send messages + 通知音を鳴らす + + + + + Volume + Label for a slider used to adjust audio notification volume +---------- +Name of the slider used to adjust audio notification volume for accessibility tech like screen readers + 音量 + + + + + Language + Label for combobox where users can specify the UI language +---------- +Name of the combobox used to select UI langauge for accessibility tech like screen readers + 言語 + + + + Restart Ricochet to apply changes + 変更を反映するためリコシェイを再起動する + + + + What language ricochet will use + Description of what the language combox is for for accessibility tech like screen readers + + + + + LanguagePreferences + + Select Language + 言語を選択 + + + Restart Ricochet to apply changes + 変更を反映するためリコシェイを再起動する + + + + LanguagesModel + + + System default + システムデフォルト + + + + Main + + + Ricochet Error + リコシェイのエラー + + + + MainToolBar + + + + Add Contact + Tooltip text for the button that launches the dialog box for adding a new contact +---------- +Name of the button for adding a new contact for accessibility tech like screen readers + コンタクトを追加する + + + + + Preferences + Tooltip text for the button that launches program preferences window +---------- +Name of the button for launching the preferences window for accessibility tech like screen readres + 選択 + + + + Click to add contacts + Tooltip that displays on first launch indicating how to add a new contact + クリックしてコンタクトを追加する + + + + Shows the add contact dialogue + Description of the 'Add Contact' button for accessibility tech like screen readers + + + + + Shows the preferences dialogue + Description of the 'Preferences' button for accessibility tech like screen readers + + + + + MainWindow + + + Version Seperator + + + + + Remove %1 + %1を削除する + + + + Do you want to permanently remove %1? + %1を永久に削除しますか? + + + + Main Toolbar + Name of the main toolbar for accessibility tech like screen readers + + + + + Toolbar with connection status, add contact button, and preferences button + Description of the main toolbar for accessibility tech like screen readers + + + + + Contact pane + Name of the pane holding the user's contacts for accessibility tech like screen readers + + + + + MessageDelegate + + + %1 is offline + %1 nickname + %1はオフラインです + + + + File transfer file name + Description of the text displaying the filename of a file transfer, used by accessibility tech like screen readres + + + + + File transfer progress + Description of progress bar displaying the file transfer progress, used by accessibility tech like screen readers + + + + + File transfer status + Description of label displaying the current status of a file transfer, used by accessibility tech like screen readers + + + + + Download + Label for file transfer 'Download' button for accessibility tech like screen readers + + + + + Download file + Description of what the file transfer 'Download' button does for accessibility tech like screen readers + + + + + Cancel or reject + Label for file transfer 'Cancel' button for accessibility tech like screen readers + + + + + Cancels or rejects a file transfer + Description of what the file transfer 'Cancel' button does for accessibility tech like screen readers + + + + Copy ID + Text for context menu command to copy a ricochet contact id to clipboard + IDをコピーする + + + Copy Link + Text for context menu command to copy a url to the clipboard + リンクをコピーする + + + Open with Browser + Text for context menu command to open a url in a web browser + ブラウザで開く + + + Add as Contact + コンタクトに追加する + + + + Copy Message + Text for context menu command to copy an entire message to clipboard + メッセージをコピーする + + + + Copy Selection + Text for context menu command to copy selected text to clipboard + 選択範囲をコピーする + + + + MessageDialogWrapper + + + Remove %1 + A command to remove a contact from the contact list, %1 is the contact's nickname + %1を削除する + + + + Do you want to permanently remove %1? + Confirmation of contact removal, %1 is the contact's nickname + %1を永久に削除しますか? + + + + This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. + Message describing what will happen when a contact is removed from you contact list + このコンタクトはあなたにメッセージを送ることはできなくなり、削除されたことが通知されます。新たにつながりリクエストを送ることを選択するかもしれません。 + + + + NetworkSetupWizard + + + This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. + A label with directions for when to use the 'Connect' button + このコンピュータはインターネット接続に問題がありません。トールネットワークに直接接続します + + + + Connect + Label for button to connect to the Tor network + 接続する + + + + This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. + A label with directions for when to use the 'Configure' button + このコンピュータのインターネット接続は検閲、フィルタ、プロキシ設定がされています。ネットワークコンフィギュレーションの設定を変更する必要があります。 + + + + Configure + Label for button to configure the Tor daemon beore connecting to the Tor network + 設定する + + + + OfflineStateItem + + + Configure + Button label + 設定する + + + + Details + Button label + 詳細 + + + + Connection failed + Label displayed when connecting to the Tor network fails + 接続に失敗しました + + + + Connecting… + Label displayed when in process of connecting, … is ellipsis + 接続中 + + + + OpenBrowserDialog + + <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. + Label displayed when user clicks on a link + <b> 警告!<b/> このリンクを既定のブラウザで開くとあなたのセキュリティと匿名性に害を及ぼす可能性があります。<br><br> 代わりに<a href=','> クリップボードにコピーする</a> こともできます。 + + + Don't ask again for links from %1 + %1からのリンクはいつも許可する + + + Don't ask again for any links (not recommended!) + Checkbox option text for when user clicks on a link + %1からのすべてのリンクを再確認しない(非推奨) + + + Open Browser + Label on button to open link in a web browser + ブラウザを開く + + + Cancel + Label on cancel button + 取り消す + + + + PreferencesDialog + + + Ricochet Preferences + リコシェイの設定 + + + + Menu Tabs + Name of the tab bar for accessibility tech like screen readers + + + + + General + Title of the general settings tab + 一般 + + + Language + 言語 + + + + Contacts + Title of the contacts list tab + コンタクト + + + + Tor + Title of the tor tab, contains tor settings and logs + トール + + + + About + Title of the about tab, contains license information and ricochet version + 情報 + + + + QCocoaMenuItem + + + Preference + 設定 + + + + StartupStatusPage + + + The Tor process was not started successfully. This is most likely an installation or system error. + Error status string displayed when tor daemon does not launch successfully + トールの処理が適切に開始されませんでした。インストールエラーか、システムエラーの可能性が高いです。 + + + + Quit + Button title to quit/terminate the program + 終了する + + + + TorBootstrapStatus + + + Connecting to the Tor network… + Text status displayed when tor daemon is connecting, … is ellipsis + トールネットワークに接続中 + + + + Connection status + Description of the connection status text, used by accessibility tech like screen readers + + + + + Connection progress + Description of the bootstrap progress bar, used by accessibility tech like screen readers + + + + + + Back + Label for button which moves us back to previous screen + ドル + + + + Hide Details + Label for button which hides the tor logs + + + + + Show Details + Label for button which shows the tor logs + + + + + Hides the tor progress log + Description of hide details button, used by accessibility tech like screen readers + + + + + Shows the tor progress log + Description of show details button, used by accessibility tech like screen readers + + + + Hide details + 詳細を隠す + + + Show details + 詳細を開く + + + + Done + Label for button which closes the tor connection window + 完了 + + + + TorConfigurationPage + + + Does this computer need a proxy to access the internet? + このコンピュータはインターネットにアクセスするのにプロキシが必要ですか? + + + + Proxy type: + プロキシの種類 + + + + + None + 無い + + + + If you need a proxy to access the internet, select one from this list. + Description used by accessibility tech, such as screen readers + + + + + Address: + Label indicating the textbox to place a proxy IP or URL + アドレス + + + + IP address or hostname + Placeholder text of text box expecting an IP or URL for proxy + IPアドレスかホストネーム + + + + Enter the IP address or hostname of the proxy you wish to connect to + Description of what to enter into the IP textbox, used by accessibility tech such as screen readers + + + + + Port: + Label indicating the textbox to place a proxy port + ポート + + + + Port + Name of the port label, used by accessibility tech such as screen readers + + + + + Enter the port of the proxy you wish to connect to + Description of what to enter into the Port textbox, used by accessibility tech such as screen readers + + + + + Username: + Label indicating the textbox to place the proxy username + ユーザーネーム + + + + + Optional + Textbox placeholder text indicating the field is not required + 必須では無い + + + + Username + Name of the username label, used by accessibility tech such as screen readers + + + + + If required, enter the username for the proxy you wish to connect to + Description to enter into the Username textbox, used by accessibility tech such as screen readers + + + + + Password: + Label indicating the textbox to place the proxy password + パスワード + + + + Password + Name of the password label, used by accessibility tech such as screen readers + + + + + If required, enter the password for the proxy you wish to connect to + Description to enter into the Password textbox, used by accessibility tech such as screen readers + + + + + Does this computer's Internet connection go through a firewall that only allows connections to certain ports? + Description for the purpose of the Allowed Ports textbox + このコンピュータは特定のポートの身を許可するファイアウォールを経由しますか? + + + + Allowed ports: + Label indicating the textbox to place the allowed ports + 許可されたポート + + + + Example: 80,443 + Textbox showing an example entry for the firewall allowed ports entry + 例えば:80 、443 + + + + Allowed ports + Name of the allowed ports label, used by accessibility tech such as screen readers + + + + + If this computer's Internet connection is censored, you will need to obtain and use bridge relays. + このコンピュータのインターネット接続が検知された場合、ブリッジリレーを手に入れて利用する必要があります + + + + + Enter one or more bridge relays (one per line): + 1つもしくはそれ以上のブリッジリレーを入力してください(1ラインにつき1つ): + + + + Back + Button label for going back to previous screen + 戻る + + + + Connect + Button label for connecting to tor + 接続する + + + + TorLogDisplay + + + Tor log + Name of the text field containg the tor logs, used by accessibility tech such as screen readers + + + + + TorPreferences + + + Running: + Display label that beside it indicates whether tor is running + 実行中: + + + + Running + + + + + Whether tor is running + Description of the value of this label, used by acccessibility tech like screen readers + + + + + + Yes + はい + + + + + No + いいえ + + + External + 外部の + + + + Control connected: + Display label that beside it indicates whether ricochet is connected to tor's control port + 制御された接続 + + + + Control connected + + + + + Whether tor control connected + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Circuits established: + 回線の成立 + + + + Circuits established + + + + + Whether circuits established + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Hidden service: + 秘匿サービス + + + + Hidden service + + + + + Online + オンライン + + + + Offline + オフライン + + + + Whether a hidden service is up or not + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Version: + バージョン + + + + Version + + + + + Error: <b>%1</b> + %1 is error message + エラー:<b> %1</b> + + + + Configure + 設定 + + + + TorStateWidget + + + + Connection failed + 接続に失敗しました + + + + + Connecting… + … is ellipsis + 接続中 + + + + Connecting… (%1%) + %1 is progress percentage, e.g. 100 + 接続中(%1%) + + + + Online + オンライン + + + + Connected + 接続済 + + + + Tor status + + + + + shims::ConversationModel + + + Pending + + + + + Accepted + 受諾されました + + + + Rejected + 拒否されました + + + + Cancelled + + + + + Complete + + + + + Unkown Failure + + + + + Bad File Hash + + + + + Network Error + + + + + File System Error + + + + + Invalid + + + + + + Open File + + + + + + + + me + + + + + Save File + + + + diff --git a/src/ricochet-refresh/translation/ricochet_nb.ts b/src/ricochet-refresh/translation/ricochet_nb.ts new file mode 100644 index 00000000..dc336287 --- /dev/null +++ b/src/ricochet-refresh/translation/ricochet_nb.ts @@ -0,0 +1,1257 @@ + + + + + AboutPreferences + + Ricochet %1 + %1 version, e.g. 1.0.0 + Ricochet %1 + + + + Ricochet-Refresh %1 + %1 version, e.g. 1.0.0 + + + + + Ricochet Refresh version %1 + provides a readable interpretation of the Ricochet-Refresh version number for accessibility tech like screen readers + + + + + The license of Ricochet Refresh and its dependencies + + + + + License + + + + + About + Om + + + + About page, contains license and version information + summary of the window's contents for accessibility tech like screen readers + + + + + Current Ricochet-Refresh version + + + + + AddContactDialog + + + Share your Ricochet ID to allow connection requests + tells the user the purpose of their Ricochet ID, which is basically a username + Del din Ricochet-ID for å tillate tilkoblingsforespørsler + + + + Cancel + label for button which dismisses a dialog + Avbryt + + + + Closes the contact add window + description for 'Cancel' button accessibility tech like screen readers + + + + + Add + button label to finish adding a contact/friend + Legg til + + + + Adds the contact to your contact list + description for 'Add' button for accessibility tech like screen readres + + + + + ChatPage + + + Message area + label for accessibility tech like screen readers + + + + + Write the message to be sent here. Press enter to send + description of the text area used to send messages for accessibility tech like screen readers + + + + + ContactActions + + + Warning + + + + + Could not successfully export conversation + + + + + Open Window + Context menu command to open the chat screen in a separate window + Åpne vindu + + + + Details... + Context menu command to open a window showing the selected contact's details + Detaljer... + + + + Rename + Context menu command to rename the selected contact + Endre navn + + + + Send File... + Context menu command to initiate a file transfer, opens a system file dialog + + + + + Export Conversation + Context menu command to initiate a chat log export, opens a system file dialog to export to + + + + + Remove + Context menu command to remove a contact from the contact list + Fjern + + + + Contact options + Description of the items in the context menu for accessibility tech like screen readers + + + + + ContactIDField + + + <b>%1</b> is already your contact + Error message showed when user attempts to add a contact already in their contact list + <b>%1</b> er allerede din kontakt + + + + This ID is invalid + Error message showed when the id doesn't comply with spec https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt + + + + + You can't add yourself as a contact + Error message showed when user attempts to add themselves as a contact in their contact list + Du kan ikke legge til deg selv som en kontakt + + + + Enter an ID starting with <b>ricochet:</b> + Error message showed when the provided ricochet id is invalid + Skriv inn en ID som starter med <b>ricochet:</b> + + + + Copied to clipboard + Message displayed when text is copied to the user's clipboard + Kopiert til utklippstavlen + + + + Copy + Text displayed on a button used to copy somethign to the user's clipboard + Kopier + + + + Copies the ricochet id to the clipboard + Text description of ricochet id copy button for accessibility tech like screen readers + + + + + ContactList + + + Online + Section header in the contact list for users which are online + Tilkoblet + + + + Offline + Section header in the contact list for users which are offline + Frakoblet + + + + Requests + Section header in the contact list for users requesting to be added to the user's contact list + Forespørsler + + + + Rejected + Section header in the contact list for users that have rejected the user's request to be added to their contact list + Avvist + + + + Status for the given contacts + Description of the section header for accessibility tech like screen readers + + + + Outdated + Utdatert + + + + ContactPreferences + + Date added: + Dato lagt til: + + + Last seen: + Sist sett: + + + Request: + Forespørsel: + + + Pending connection + I påvente av forbindelse + + + Delivered + Levert + + + Accepted + Godtatt + + + Error + Feil + + + Rejected + Avvist + + + %1 (Connected) + %1 status, e.g. "Accepted" + %1 (tilkoblet) + + + Response: + Respons: + + + + Contact list + Description of the list of contacts for accessibility tech like screen readers + + + + + Contact ID for + Description of text box containing a contact's contact id for accessibility tech like screen readers + + + + + + Unknown user + A placeholder name for a contact whose name we do not know + + + + + Rename + Label for button which allows renaming of a contact + Endre navn + + + + Renames this contact + Description of button which renames a contact for accessibility tech like screen readers + + + + + Remove + Label for button which removes a contact from the contact list + Fjern + + + + Removes this contact + Description of button which removes a user from the contact list for accessibility tech like screen readers + + + + + Preferences for contact + Description of the contents of the 'Contacts' window for accessibility tech like screen readers + + + + + Contact Preferences Window + Name of the contact preferences window for accessibility tech like screen readers + + + + + A list of all your contacts, with their ricochet IDs, and options such as renaming and removing + Description of what user can do in the contact preferences window for accessibility tech like screen readers + + + + + ContactRequestDialog + + + Someone new is asking to connect to you + Descriptive text that is displayed in a popup window when a user tries to add you as a contact + Noen nye spør om de kan å få koble til + + + + Reject + Label for button which rejects a contact request when pressed + Avvist + + + + Rejects the incoming contact request + Description of what 'Reject' button does for accessibility tech like screen readers + + + + + Accept + Label for button which accepts a contact request when pressed + Godta + + + + Accepts the incoming contact request + Description of what 'Accept' button does for accessibility tech like screen readers + + + + + ContactRequestFields + + + ID: + Label for the contact id text box in the 'add new contact' window + ID: + + + + Name: + Label for the contact nickname text box in the 'add new contact' window + Navn: + + + + Field for the contact's nickname + Description of textbox for setting a contact's nickname for accessibility tech like screen readers + + + + + Message: + Label for the contact greeting message text box in the 'add new contact' window + Melding: + + + + Field for the contact's greeting message + Description of textbox for setting a new contact's initial greeting message for accessibility tech like screen readers + + + + + GeneralPreferences + + + Use a single window for conversations + Text description of an option to use one single program window for the contact list and the chats + Bruk ètt vindu for samtaler + + + Open links in default browser without prompting + Text description of an option to open URLs in default web browser when clicked + Åpne lenker i standardnettleser uten å spørre + + + + Play audio notifications + Text description of an option to play audio notifications when contacts log in, log out, and send messages + Spill av lydvarsler + + + + + Volume + Label for a slider used to adjust audio notification volume +---------- +Name of the slider used to adjust audio notification volume for accessibility tech like screen readers + Volum + + + + + Language + Label for combobox where users can specify the UI language +---------- +Name of the combobox used to select UI langauge for accessibility tech like screen readers + Språk + + + + Restart Ricochet to apply changes + Ta en omstart av Ricochet for å bruke endringene + + + + What language ricochet will use + Description of what the language combox is for for accessibility tech like screen readers + + + + + LanguagePreferences + + Select Language + Velg språk + + + Restart Ricochet to apply changes + Ta en omstart av Ricochet for å bruke endringene + + + + LanguagesModel + + + System default + Systemstandard + + + + Main + + + Ricochet Error + Ricochet-feil + + + + MainToolBar + + + + Add Contact + Tooltip text for the button that launches the dialog box for adding a new contact +---------- +Name of the button for adding a new contact for accessibility tech like screen readers + Legg til kontakt + + + + + Preferences + Tooltip text for the button that launches program preferences window +---------- +Name of the button for launching the preferences window for accessibility tech like screen readres + Preferanser + + + + Click to add contacts + Tooltip that displays on first launch indicating how to add a new contact + Trykk for å legge til kontakter + + + + Shows the add contact dialogue + Description of the 'Add Contact' button for accessibility tech like screen readers + + + + + Shows the preferences dialogue + Description of the 'Preferences' button for accessibility tech like screen readers + + + + + MainWindow + + + Version Seperator + + + + + Remove %1 + Fjern %1 + + + + Do you want to permanently remove %1? + Ønsker du å slette %1 for alltid? + + + + Main Toolbar + Name of the main toolbar for accessibility tech like screen readers + + + + + Toolbar with connection status, add contact button, and preferences button + Description of the main toolbar for accessibility tech like screen readers + + + + + Contact pane + Name of the pane holding the user's contacts for accessibility tech like screen readers + + + + + MessageDelegate + + + %1 is offline + %1 nickname + %1 er frakoblet + + + + File transfer file name + Description of the text displaying the filename of a file transfer, used by accessibility tech like screen readres + + + + + File transfer progress + Description of progress bar displaying the file transfer progress, used by accessibility tech like screen readers + + + + + File transfer status + Description of label displaying the current status of a file transfer, used by accessibility tech like screen readers + + + + + Download + Label for file transfer 'Download' button for accessibility tech like screen readers + + + + + Download file + Description of what the file transfer 'Download' button does for accessibility tech like screen readers + + + + + Cancel or reject + Label for file transfer 'Cancel' button for accessibility tech like screen readers + + + + + Cancels or rejects a file transfer + Description of what the file transfer 'Cancel' button does for accessibility tech like screen readers + + + + Copy ID + Text for context menu command to copy a ricochet contact id to clipboard + Kopier ID + + + Copy Link + Text for context menu command to copy a url to the clipboard + Kopier lenke + + + Open with Browser + Text for context menu command to open a url in a web browser + Åpne i nettleser + + + Add as Contact + Legg til som kontakt + + + + Copy Message + Text for context menu command to copy an entire message to clipboard + Kopier melding + + + + Copy Selection + Text for context menu command to copy selected text to clipboard + Kopier utvalg + + + + MessageDialogWrapper + + + Remove %1 + A command to remove a contact from the contact list, %1 is the contact's nickname + Fjern %1 + + + + Do you want to permanently remove %1? + Confirmation of contact removal, %1 is the contact's nickname + Ønsker du å permanent fjerne %1? + + + + This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. + Message describing what will happen when a contact is removed from you contact list + Denne kontakten vil ikke lengre være i stand til å kunne kontakte deg og er blitt varslet om dette. Vedkommende kan velge å sende ny tilkoblingsforespørsel. + + + + NetworkSetupWizard + + + This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. + A label with directions for when to use the 'Connect' button + Denne datamaskinens internettforbindelse er fri for hindre. Jeg ønsker å koble direkte til Tor-nettverket. + + + + Connect + Label for button to connect to the Tor network + Koble til + + + + This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. + A label with directions for when to use the 'Configure' button + Denne datamaskinens internettforbindelse er sensurert, filtrert, eller bak proxy. Jeg trenger å konfigurere nettverksinnstillinger. + + + + Configure + Label for button to configure the Tor daemon beore connecting to the Tor network + Konfigurer + + + + OfflineStateItem + + + Configure + Button label + Konfigurer + + + + Details + Button label + Detaljer + + + + Connection failed + Label displayed when connecting to the Tor network fails + Tilkoblingsfeil + + + + Connecting… + Label displayed when in process of connecting, … is ellipsis + Kobler til... + + + + OpenBrowserDialog + + <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. + Label displayed when user clicks on a link + <b>Advarsel!</b> Åpning av lenker i din standardnettleser vil svekke sikkerheten og minske anonymiteten din.<br><br>Du kan <a href='.'>kopiere til utklippstavlen</a> i stedet for. + + + Don't ask again for links from %1 + Ikke spør igjen for lenker fra %1 + + + Don't ask again for any links (not recommended!) + Checkbox option text for when user clicks on a link + Ikke spør igjen for noen lenker som helst (ikke anbefalt!) + + + Open Browser + Label on button to open link in a web browser + Åpne nettleser + + + Cancel + Label on cancel button + Avbryt + + + + PreferencesDialog + + + Ricochet Preferences + Ricochet-preferanser + + + + Menu Tabs + Name of the tab bar for accessibility tech like screen readers + + + + + General + Title of the general settings tab + Generelt + + + Language + Språk + + + + Contacts + Title of the contacts list tab + Kontakter + + + + Tor + Title of the tor tab, contains tor settings and logs + Tor + + + + About + Title of the about tab, contains license information and ricochet version + Om + + + + QCocoaMenuItem + + + Preference + Preferanse + + + + StartupStatusPage + + + The Tor process was not started successfully. This is most likely an installation or system error. + Error status string displayed when tor daemon does not launch successfully + Tor-prosessen ble ikke startet vellykket. Dette er mest sannsynlig en installasjons- eller systemfeil. + + + + Quit + Button title to quit/terminate the program + Avslutt + + + + TorBootstrapStatus + + + Connecting to the Tor network… + Text status displayed when tor daemon is connecting, … is ellipsis + Kobler til Tor-nettverket + + + + Connection status + Description of the connection status text, used by accessibility tech like screen readers + + + + + Connection progress + Description of the bootstrap progress bar, used by accessibility tech like screen readers + + + + + + Back + Label for button which moves us back to previous screen + Tilbake + + + + Hide Details + Label for button which hides the tor logs + + + + + Show Details + Label for button which shows the tor logs + + + + + Hides the tor progress log + Description of hide details button, used by accessibility tech like screen readers + + + + + Shows the tor progress log + Description of show details button, used by accessibility tech like screen readers + + + + Hide details + Gjem detaljer + + + Show details + Vis detaljer + + + + Done + Label for button which closes the tor connection window + Ferdig + + + + TorConfigurationPage + + + Does this computer need a proxy to access the internet? + Trenger denne datamaskinen proxy for å få tilgang til Internett? + + + + Proxy type: + Proxytype: + + + + + None + Ingen + + + + If you need a proxy to access the internet, select one from this list. + Description used by accessibility tech, such as screen readers + + + + + Address: + Label indicating the textbox to place a proxy IP or URL + Adresse: + + + + IP address or hostname + Placeholder text of text box expecting an IP or URL for proxy + IP-adresse eller vertsnavn + + + + Enter the IP address or hostname of the proxy you wish to connect to + Description of what to enter into the IP textbox, used by accessibility tech such as screen readers + + + + + Port: + Label indicating the textbox to place a proxy port + Port: + + + + Port + Name of the port label, used by accessibility tech such as screen readers + + + + + Enter the port of the proxy you wish to connect to + Description of what to enter into the Port textbox, used by accessibility tech such as screen readers + + + + + Username: + Label indicating the textbox to place the proxy username + Brukernavn: + + + + + Optional + Textbox placeholder text indicating the field is not required + Valgfri + + + + Username + Name of the username label, used by accessibility tech such as screen readers + + + + + If required, enter the username for the proxy you wish to connect to + Description to enter into the Username textbox, used by accessibility tech such as screen readers + + + + + Password: + Label indicating the textbox to place the proxy password + Passord: + + + + Password + Name of the password label, used by accessibility tech such as screen readers + + + + + If required, enter the password for the proxy you wish to connect to + Description to enter into the Password textbox, used by accessibility tech such as screen readers + + + + + Does this computer's Internet connection go through a firewall that only allows connections to certain ports? + Description for the purpose of the Allowed Ports textbox + Går denne datamaskinens internettforbindelse gjennom en brannmur som kun tillater forbindelser til bestemte porter? + + + + Allowed ports: + Label indicating the textbox to place the allowed ports + Tillatte porter: + + + + Example: 80,443 + Textbox showing an example entry for the firewall allowed ports entry + Eksempel: 80,443 + + + + Allowed ports + Name of the allowed ports label, used by accessibility tech such as screen readers + + + + + If this computer's Internet connection is censored, you will need to obtain and use bridge relays. + Hvis denne datamaskinens internettforbindelse er sensurert må du få tak i og bruke broreléer. + + + + + Enter one or more bridge relays (one per line): + Skriv inn et eller flere broreléer (et relé per linje): + + + + Back + Button label for going back to previous screen + Tilbake + + + + Connect + Button label for connecting to tor + Koble til + + + + TorLogDisplay + + + Tor log + Name of the text field containg the tor logs, used by accessibility tech such as screen readers + + + + + TorPreferences + + + Running: + Display label that beside it indicates whether tor is running + Kjører: + + + + Running + + + + + Whether tor is running + Description of the value of this label, used by acccessibility tech like screen readers + + + + + + Yes + Ja + + + + + No + Nei + + + External + Ekstern + + + + Control connected: + Display label that beside it indicates whether ricochet is connected to tor's control port + Kontroll tilkoblet: + + + + Control connected + + + + + Whether tor control connected + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Circuits established: + Etablerte kretser: + + + + Circuits established + + + + + Whether circuits established + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Hidden service: + Skjult tjeneste: + + + + Hidden service + + + + + Online + Pålogget + + + + Offline + Frakoblet + + + + Whether a hidden service is up or not + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Version: + Versjon: + + + + Version + + + + + Error: <b>%1</b> + %1 is error message + Feil: <b>%1</b> + + + + Configure + Konfigurer + + + + TorStateWidget + + + + Connection failed + Tilkoblingsfeil + + + + + Connecting… + … is ellipsis + Kobler til... + + + + Connecting… (%1%) + %1 is progress percentage, e.g. 100 + Kobler til... (%1%) + + + + Online + Pålogget + + + + Connected + Tilkoblet + + + + Tor status + + + + + shims::ConversationModel + + + Pending + + + + + Accepted + Godtatt + + + + Rejected + Avvist + + + + Cancelled + + + + + Complete + + + + + Unkown Failure + + + + + Bad File Hash + + + + + Network Error + + + + + File System Error + + + + + Invalid + + + + + + Open File + + + + + + + + me + + + + + Save File + + + + diff --git a/src/ricochet-refresh/translation/ricochet_nl_NL.ts b/src/ricochet-refresh/translation/ricochet_nl_NL.ts new file mode 100644 index 00000000..dda85693 --- /dev/null +++ b/src/ricochet-refresh/translation/ricochet_nl_NL.ts @@ -0,0 +1,1257 @@ + + + + + AboutPreferences + + Ricochet %1 + %1 version, e.g. 1.0.0 + Ricochet %1 + + + + Ricochet-Refresh %1 + %1 version, e.g. 1.0.0 + + + + + Ricochet Refresh version %1 + provides a readable interpretation of the Ricochet-Refresh version number for accessibility tech like screen readers + + + + + The license of Ricochet Refresh and its dependencies + + + + + License + + + + + About + Over + + + + About page, contains license and version information + summary of the window's contents for accessibility tech like screen readers + + + + + Current Ricochet-Refresh version + + + + + AddContactDialog + + + Share your Ricochet ID to allow connection requests + tells the user the purpose of their Ricochet ID, which is basically a username + Deel je Ricochet ID om verbindingsverzoeken mogelijk te maken + + + + Cancel + label for button which dismisses a dialog + Annuleren + + + + Closes the contact add window + description for 'Cancel' button accessibility tech like screen readers + + + + + Add + button label to finish adding a contact/friend + Toevoegen + + + + Adds the contact to your contact list + description for 'Add' button for accessibility tech like screen readres + + + + + ChatPage + + + Message area + label for accessibility tech like screen readers + + + + + Write the message to be sent here. Press enter to send + description of the text area used to send messages for accessibility tech like screen readers + + + + + ContactActions + + + Warning + + + + + Could not successfully export conversation + + + + + Open Window + Context menu command to open the chat screen in a separate window + Open venster + + + + Details... + Context menu command to open a window showing the selected contact's details + Details... + + + + Rename + Context menu command to rename the selected contact + Hernoemen + + + + Send File... + Context menu command to initiate a file transfer, opens a system file dialog + + + + + Export Conversation + Context menu command to initiate a chat log export, opens a system file dialog to export to + + + + + Remove + Context menu command to remove a contact from the contact list + Verwijderen + + + + Contact options + Description of the items in the context menu for accessibility tech like screen readers + + + + + ContactIDField + + + <b>%1</b> is already your contact + Error message showed when user attempts to add a contact already in their contact list + <b>%1</b> is al een contact + + + + This ID is invalid + Error message showed when the id doesn't comply with spec https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt + + + + + You can't add yourself as a contact + Error message showed when user attempts to add themselves as a contact in their contact list + Je kunt jezelf niet als contact toevoegen + + + + Enter an ID starting with <b>ricochet:</b> + Error message showed when the provided ricochet id is invalid + Geef een ID op dat begint met <b>ricochet:</b> + + + + Copied to clipboard + Message displayed when text is copied to the user's clipboard + Gekopieerd naar klembord + + + + Copy + Text displayed on a button used to copy somethign to the user's clipboard + Kopieer + + + + Copies the ricochet id to the clipboard + Text description of ricochet id copy button for accessibility tech like screen readers + + + + + ContactList + + + Online + Section header in the contact list for users which are online + Online + + + + Offline + Section header in the contact list for users which are offline + Offline + + + + Requests + Section header in the contact list for users requesting to be added to the user's contact list + Aanvragen + + + + Rejected + Section header in the contact list for users that have rejected the user's request to be added to their contact list + Afgewezen + + + + Status for the given contacts + Description of the section header for accessibility tech like screen readers + + + + Outdated + Verouderd + + + + ContactPreferences + + Date added: + Datum toegevoegd: + + + Last seen: + Laatst gezien: + + + Request: + Aanvraag: + + + Pending connection + Verbinding wachtend + + + Delivered + Afgeleverd + + + Accepted + Geaccepteerd + + + Error + Fout + + + Rejected + Afgewezen + + + %1 (Connected) + %1 status, e.g. "Accepted" + %1 (verbonden) + + + Response: + Antwoord: + + + + Contact list + Description of the list of contacts for accessibility tech like screen readers + + + + + Contact ID for + Description of text box containing a contact's contact id for accessibility tech like screen readers + + + + + + Unknown user + A placeholder name for a contact whose name we do not know + + + + + Rename + Label for button which allows renaming of a contact + Hernoemen + + + + Renames this contact + Description of button which renames a contact for accessibility tech like screen readers + + + + + Remove + Label for button which removes a contact from the contact list + Verwijder + + + + Removes this contact + Description of button which removes a user from the contact list for accessibility tech like screen readers + + + + + Preferences for contact + Description of the contents of the 'Contacts' window for accessibility tech like screen readers + + + + + Contact Preferences Window + Name of the contact preferences window for accessibility tech like screen readers + + + + + A list of all your contacts, with their ricochet IDs, and options such as renaming and removing + Description of what user can do in the contact preferences window for accessibility tech like screen readers + + + + + ContactRequestDialog + + + Someone new is asking to connect to you + Descriptive text that is displayed in a popup window when a user tries to add you as a contact + Een nieuwe contactpersoon wil met je verbinden + + + + Reject + Label for button which rejects a contact request when pressed + Afwijzen + + + + Rejects the incoming contact request + Description of what 'Reject' button does for accessibility tech like screen readers + + + + + Accept + Label for button which accepts a contact request when pressed + Accepteren + + + + Accepts the incoming contact request + Description of what 'Accept' button does for accessibility tech like screen readers + + + + + ContactRequestFields + + + ID: + Label for the contact id text box in the 'add new contact' window + ID: + + + + Name: + Label for the contact nickname text box in the 'add new contact' window + Naam: + + + + Field for the contact's nickname + Description of textbox for setting a contact's nickname for accessibility tech like screen readers + + + + + Message: + Label for the contact greeting message text box in the 'add new contact' window + Bericht: + + + + Field for the contact's greeting message + Description of textbox for setting a new contact's initial greeting message for accessibility tech like screen readers + + + + + GeneralPreferences + + + Use a single window for conversations + Text description of an option to use one single program window for the contact list and the chats + Gebruik een enkel venster voor gesprekken + + + Open links in default browser without prompting + Text description of an option to open URLs in default web browser when clicked + Open links in standaardbrowser zonder te vragen + + + + Play audio notifications + Text description of an option to play audio notifications when contacts log in, log out, and send messages + Afspelen audio meldingen + + + + + Volume + Label for a slider used to adjust audio notification volume +---------- +Name of the slider used to adjust audio notification volume for accessibility tech like screen readers + Volume + + + + + Language + Label for combobox where users can specify the UI language +---------- +Name of the combobox used to select UI langauge for accessibility tech like screen readers + Taal + + + + Restart Ricochet to apply changes + Herstart Ricochet om wijzigingen door te voeren + + + + What language ricochet will use + Description of what the language combox is for for accessibility tech like screen readers + + + + + LanguagePreferences + + Select Language + Kies taal + + + Restart Ricochet to apply changes + Herstart Ricochet om wijzigingen door te voeren + + + + LanguagesModel + + + System default + Systeemstandaard + + + + Main + + + Ricochet Error + Ricochet fout + + + + MainToolBar + + + + Add Contact + Tooltip text for the button that launches the dialog box for adding a new contact +---------- +Name of the button for adding a new contact for accessibility tech like screen readers + Toevoegen contact + + + + + Preferences + Tooltip text for the button that launches program preferences window +---------- +Name of the button for launching the preferences window for accessibility tech like screen readres + Voorkeuren + + + + Click to add contacts + Tooltip that displays on first launch indicating how to add a new contact + Klikken om contacten toe te voegen + + + + Shows the add contact dialogue + Description of the 'Add Contact' button for accessibility tech like screen readers + + + + + Shows the preferences dialogue + Description of the 'Preferences' button for accessibility tech like screen readers + + + + + MainWindow + + + Version Seperator + + + + + Remove %1 + Verwijder %1 + + + + Do you want to permanently remove %1? + Wil je %1 permanent verwijderen? + + + + Main Toolbar + Name of the main toolbar for accessibility tech like screen readers + + + + + Toolbar with connection status, add contact button, and preferences button + Description of the main toolbar for accessibility tech like screen readers + + + + + Contact pane + Name of the pane holding the user's contacts for accessibility tech like screen readers + + + + + MessageDelegate + + + %1 is offline + %1 nickname + %1 is offline + + + + File transfer file name + Description of the text displaying the filename of a file transfer, used by accessibility tech like screen readres + + + + + File transfer progress + Description of progress bar displaying the file transfer progress, used by accessibility tech like screen readers + + + + + File transfer status + Description of label displaying the current status of a file transfer, used by accessibility tech like screen readers + + + + + Download + Label for file transfer 'Download' button for accessibility tech like screen readers + + + + + Download file + Description of what the file transfer 'Download' button does for accessibility tech like screen readers + + + + + Cancel or reject + Label for file transfer 'Cancel' button for accessibility tech like screen readers + + + + + Cancels or rejects a file transfer + Description of what the file transfer 'Cancel' button does for accessibility tech like screen readers + + + + Copy ID + Text for context menu command to copy a ricochet contact id to clipboard + Kopieer ID + + + Copy Link + Text for context menu command to copy a url to the clipboard + Kopieer link + + + Open with Browser + Text for context menu command to open a url in a web browser + Open met browser + + + Add as Contact + Toevoegen als contact + + + + Copy Message + Text for context menu command to copy an entire message to clipboard + Kopiëren bericht + + + + Copy Selection + Text for context menu command to copy selected text to clipboard + Kopiëren selectie + + + + MessageDialogWrapper + + + Remove %1 + A command to remove a contact from the contact list, %1 is the contact's nickname + Verwijder %1 + + + + Do you want to permanently remove %1? + Confirmation of contact removal, %1 is the contact's nickname + Wil je %1 permanent verwijderen? + + + + This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. + Message describing what will happen when a contact is removed from you contact list + Deze contactpersoon kan je niet langer berichten sturen en wordt geïnformeerd over het verwijderen. Hij/zij kan een nieuwe aanvraag doen. + + + + NetworkSetupWizard + + + This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. + A label with directions for when to use the 'Connect' button + De internetverbinding van deze computer kent geen belemmeringen. Ik wil rechtstreeks verbinden met het TOR netwerk. + + + + Connect + Label for button to connect to the Tor network + Verbinden + + + + This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. + A label with directions for when to use the 'Configure' button + De internetverbinding van deze computer is gecensureerd, gefilterd of geproxied. Ik moet de netwerkinstellingen configureren. + + + + Configure + Label for button to configure the Tor daemon beore connecting to the Tor network + Configureren + + + + OfflineStateItem + + + Configure + Button label + Configureren + + + + Details + Button label + Details + + + + Connection failed + Label displayed when connecting to the Tor network fails + Verbinding mislukt + + + + Connecting… + Label displayed when in process of connecting, … is ellipsis + Verbinden... + + + + OpenBrowserDialog + + <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. + Label displayed when user clicks on a link + <b>Waarschuwing!</b> Het openen van links met je standaard browser schaadt je beveliging en anonimiteit.<br><br>Je kunt in plaats daarvan <a href='.'>kopiëren maar het klembord</a>. + + + Don't ask again for links from %1 + Vraag niet opnieuw om links van %1 + + + Don't ask again for any links (not recommended!) + Checkbox option text for when user clicks on a link + Vraag niet meer naar links (niet aanbevolen!) + + + Open Browser + Label on button to open link in a web browser + Open Browser + + + Cancel + Label on cancel button + Annuleren + + + + PreferencesDialog + + + Ricochet Preferences + Ricochet voorkeuren + + + + Menu Tabs + Name of the tab bar for accessibility tech like screen readers + + + + + General + Title of the general settings tab + Algemeen + + + Language + Taal + + + + Contacts + Title of the contacts list tab + Contacten + + + + Tor + Title of the tor tab, contains tor settings and logs + Tor + + + + About + Title of the about tab, contains license information and ricochet version + Over + + + + QCocoaMenuItem + + + Preference + Voorkeur + + + + StartupStatusPage + + + The Tor process was not started successfully. This is most likely an installation or system error. + Error status string displayed when tor daemon does not launch successfully + Het TOR proces is niet succesvol gestart. Dit komt waarschijnlijk door een installatie- of systeemfout. + + + + Quit + Button title to quit/terminate the program + Afsluiten + + + + TorBootstrapStatus + + + Connecting to the Tor network… + Text status displayed when tor daemon is connecting, … is ellipsis + Verbinden met het TOR netwerk... + + + + Connection status + Description of the connection status text, used by accessibility tech like screen readers + + + + + Connection progress + Description of the bootstrap progress bar, used by accessibility tech like screen readers + + + + + + Back + Label for button which moves us back to previous screen + Terug + + + + Hide Details + Label for button which hides the tor logs + + + + + Show Details + Label for button which shows the tor logs + + + + + Hides the tor progress log + Description of hide details button, used by accessibility tech like screen readers + + + + + Shows the tor progress log + Description of show details button, used by accessibility tech like screen readers + + + + Hide details + Verberg details + + + Show details + Toon details + + + + Done + Label for button which closes the tor connection window + Gedaan + + + + TorConfigurationPage + + + Does this computer need a proxy to access the internet? + Heeft deze computer een proxy nodig om te verbinden met het internet? + + + + Proxy type: + Proxy type: + + + + + None + Geen + + + + If you need a proxy to access the internet, select one from this list. + Description used by accessibility tech, such as screen readers + + + + + Address: + Label indicating the textbox to place a proxy IP or URL + Adres: + + + + IP address or hostname + Placeholder text of text box expecting an IP or URL for proxy + IP adres of servernaam + + + + Enter the IP address or hostname of the proxy you wish to connect to + Description of what to enter into the IP textbox, used by accessibility tech such as screen readers + + + + + Port: + Label indicating the textbox to place a proxy port + Poort: + + + + Port + Name of the port label, used by accessibility tech such as screen readers + + + + + Enter the port of the proxy you wish to connect to + Description of what to enter into the Port textbox, used by accessibility tech such as screen readers + + + + + Username: + Label indicating the textbox to place the proxy username + Gebruikersnaam: + + + + + Optional + Textbox placeholder text indicating the field is not required + Optioneel + + + + Username + Name of the username label, used by accessibility tech such as screen readers + + + + + If required, enter the username for the proxy you wish to connect to + Description to enter into the Username textbox, used by accessibility tech such as screen readers + + + + + Password: + Label indicating the textbox to place the proxy password + Wachtwoord: + + + + Password + Name of the password label, used by accessibility tech such as screen readers + + + + + If required, enter the password for the proxy you wish to connect to + Description to enter into the Password textbox, used by accessibility tech such as screen readers + + + + + Does this computer's Internet connection go through a firewall that only allows connections to certain ports? + Description for the purpose of the Allowed Ports textbox + Gaat de internetverbinding van deze computer door een firewall die alleen verbindingen naar bepaalde poorten toestaat? + + + + Allowed ports: + Label indicating the textbox to place the allowed ports + Toegestane poorten: + + + + Example: 80,443 + Textbox showing an example entry for the firewall allowed ports entry + Voorbeeld: 80,443 + + + + Allowed ports + Name of the allowed ports label, used by accessibility tech such as screen readers + + + + + If this computer's Internet connection is censored, you will need to obtain and use bridge relays. + Als de internetverbinding van deze computer is gecensureerd, moet je bridge relays vinden en gebruiken. + + + + + Enter one or more bridge relays (one per line): + Geen een of meer bridge relays op (een per regel): + + + + Back + Button label for going back to previous screen + Terug + + + + Connect + Button label for connecting to tor + Verbinden + + + + TorLogDisplay + + + Tor log + Name of the text field containg the tor logs, used by accessibility tech such as screen readers + + + + + TorPreferences + + + Running: + Display label that beside it indicates whether tor is running + Draaiend: + + + + Running + + + + + Whether tor is running + Description of the value of this label, used by acccessibility tech like screen readers + + + + + + Yes + Ja + + + + + No + Nee + + + External + Extern + + + + Control connected: + Display label that beside it indicates whether ricochet is connected to tor's control port + Control verbonden: + + + + Control connected + + + + + Whether tor control connected + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Circuits established: + Circuits aangelegd: + + + + Circuits established + + + + + Whether circuits established + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Hidden service: + Verborgen service: + + + + Hidden service + + + + + Online + Online + + + + Offline + Offline + + + + Whether a hidden service is up or not + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Version: + Versie: + + + + Version + + + + + Error: <b>%1</b> + %1 is error message + Fout: <b>%1</b> + + + + Configure + Configureren + + + + TorStateWidget + + + + Connection failed + Verbinding mislukt + + + + + Connecting… + … is ellipsis + Verbinden... + + + + Connecting… (%1%) + %1 is progress percentage, e.g. 100 + Verbinden… (%1%) + + + + Online + Online + + + + Connected + Verbonden + + + + Tor status + + + + + shims::ConversationModel + + + Pending + + + + + Accepted + Geaccepteerd + + + + Rejected + Afgewezen + + + + Cancelled + + + + + Complete + + + + + Unkown Failure + + + + + Bad File Hash + + + + + Network Error + + + + + File System Error + + + + + Invalid + + + + + + Open File + + + + + + + + me + + + + + Save File + + + + diff --git a/src/ricochet-refresh/translation/ricochet_pl.ts b/src/ricochet-refresh/translation/ricochet_pl.ts new file mode 100644 index 00000000..c6b4737c --- /dev/null +++ b/src/ricochet-refresh/translation/ricochet_pl.ts @@ -0,0 +1,1257 @@ + + + + + AboutPreferences + + Ricochet %1 + %1 version, e.g. 1.0.0 + Ricochet %1 + + + + Ricochet-Refresh %1 + %1 version, e.g. 1.0.0 + + + + + Ricochet Refresh version %1 + provides a readable interpretation of the Ricochet-Refresh version number for accessibility tech like screen readers + + + + + The license of Ricochet Refresh and its dependencies + + + + + License + + + + + About + O programie + + + + About page, contains license and version information + summary of the window's contents for accessibility tech like screen readers + + + + + Current Ricochet-Refresh version + + + + + AddContactDialog + + + Share your Ricochet ID to allow connection requests + tells the user the purpose of their Ricochet ID, which is basically a username + Udostępnij swój identyfikator Ricochet, aby umożliwić łączenie się z Tobą. + + + + Cancel + label for button which dismisses a dialog + Anuluj + + + + Closes the contact add window + description for 'Cancel' button accessibility tech like screen readers + + + + + Add + button label to finish adding a contact/friend + Dodaj + + + + Adds the contact to your contact list + description for 'Add' button for accessibility tech like screen readres + + + + + ChatPage + + + Message area + label for accessibility tech like screen readers + + + + + Write the message to be sent here. Press enter to send + description of the text area used to send messages for accessibility tech like screen readers + + + + + ContactActions + + + Warning + + + + + Could not successfully export conversation + + + + + Open Window + Context menu command to open the chat screen in a separate window + Otwórz okno + + + + Details... + Context menu command to open a window showing the selected contact's details + Szczegóły... + + + + Rename + Context menu command to rename the selected contact + Zmień nazwę + + + + Send File... + Context menu command to initiate a file transfer, opens a system file dialog + + + + + Export Conversation + Context menu command to initiate a chat log export, opens a system file dialog to export to + + + + + Remove + Context menu command to remove a contact from the contact list + Usuń + + + + Contact options + Description of the items in the context menu for accessibility tech like screen readers + + + + + ContactIDField + + + <b>%1</b> is already your contact + Error message showed when user attempts to add a contact already in their contact list + <b>%1</b> jest już w Twoich kontaktach + + + + This ID is invalid + Error message showed when the id doesn't comply with spec https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt + + + + + You can't add yourself as a contact + Error message showed when user attempts to add themselves as a contact in their contact list + Nie możesz dodać siebie do kontaktów + + + + Enter an ID starting with <b>ricochet:</b> + Error message showed when the provided ricochet id is invalid + Wpisz ID zaczynające się od <b>ricochet:</b> + + + + Copied to clipboard + Message displayed when text is copied to the user's clipboard + Skopiowano do schowka + + + + Copy + Text displayed on a button used to copy somethign to the user's clipboard + Kopiuj + + + + Copies the ricochet id to the clipboard + Text description of ricochet id copy button for accessibility tech like screen readers + + + + + ContactList + + + Online + Section header in the contact list for users which are online + Połączeni + + + + Offline + Section header in the contact list for users which are offline + Rozłączeni + + + + Requests + Section header in the contact list for users requesting to be added to the user's contact list + Prośby o dodanie + + + + Rejected + Section header in the contact list for users that have rejected the user's request to be added to their contact list + Odrzucone + + + + Status for the given contacts + Description of the section header for accessibility tech like screen readers + + + + Outdated + Przeterminowane + + + + ContactPreferences + + Date added: + Dołączył: + + + Last seen: + Ostatnio widziany: + + + Request: + Prośba o dodanie: + + + Pending connection + Oczekuje na połączenie + + + Delivered + Dostarczony + + + Accepted + Zaakceptowany + + + Error + Błąd + + + Rejected + Odrzucony + + + %1 (Connected) + %1 status, e.g. "Accepted" + %1 (Połączony) + + + Response: + Odpowiedź: + + + + Contact list + Description of the list of contacts for accessibility tech like screen readers + + + + + Contact ID for + Description of text box containing a contact's contact id for accessibility tech like screen readers + + + + + + Unknown user + A placeholder name for a contact whose name we do not know + + + + + Rename + Label for button which allows renaming of a contact + Zmień nazwę + + + + Renames this contact + Description of button which renames a contact for accessibility tech like screen readers + + + + + Remove + Label for button which removes a contact from the contact list + Usuń + + + + Removes this contact + Description of button which removes a user from the contact list for accessibility tech like screen readers + + + + + Preferences for contact + Description of the contents of the 'Contacts' window for accessibility tech like screen readers + + + + + Contact Preferences Window + Name of the contact preferences window for accessibility tech like screen readers + + + + + A list of all your contacts, with their ricochet IDs, and options such as renaming and removing + Description of what user can do in the contact preferences window for accessibility tech like screen readers + + + + + ContactRequestDialog + + + Someone new is asking to connect to you + Descriptive text that is displayed in a popup window when a user tries to add you as a contact + Ktoś chce się z Tobą skontaktować + + + + Reject + Label for button which rejects a contact request when pressed + Odrzuć + + + + Rejects the incoming contact request + Description of what 'Reject' button does for accessibility tech like screen readers + + + + + Accept + Label for button which accepts a contact request when pressed + Akceptuj + + + + Accepts the incoming contact request + Description of what 'Accept' button does for accessibility tech like screen readers + + + + + ContactRequestFields + + + ID: + Label for the contact id text box in the 'add new contact' window + ID: + + + + Name: + Label for the contact nickname text box in the 'add new contact' window + Nazwa: + + + + Field for the contact's nickname + Description of textbox for setting a contact's nickname for accessibility tech like screen readers + + + + + Message: + Label for the contact greeting message text box in the 'add new contact' window + Wiadomość: + + + + Field for the contact's greeting message + Description of textbox for setting a new contact's initial greeting message for accessibility tech like screen readers + + + + + GeneralPreferences + + + Use a single window for conversations + Text description of an option to use one single program window for the contact list and the chats + Używaj pojedynczego okno do rozmów + + + Open links in default browser without prompting + Text description of an option to open URLs in default web browser when clicked + Otwórz linki w domyślnej przeglądarce bez pytania + + + + Play audio notifications + Text description of an option to play audio notifications when contacts log in, log out, and send messages + Włącz powiadomienia dźwiękowe + + + + + Volume + Label for a slider used to adjust audio notification volume +---------- +Name of the slider used to adjust audio notification volume for accessibility tech like screen readers + Głośność + + + + + Language + Label for combobox where users can specify the UI language +---------- +Name of the combobox used to select UI langauge for accessibility tech like screen readers + Język + + + + Restart Ricochet to apply changes + Aby zaakceptować zmiany, uruchom Ricochet ponownie + + + + What language ricochet will use + Description of what the language combox is for for accessibility tech like screen readers + + + + + LanguagePreferences + + Select Language + Wybierz język + + + Restart Ricochet to apply changes + Aby zaakceptować zmiany, uruchom Ricochet ponownie + + + + LanguagesModel + + + System default + Domyślny w systemie + + + + Main + + + Ricochet Error + Błąd programu Ricochet + + + + MainToolBar + + + + Add Contact + Tooltip text for the button that launches the dialog box for adding a new contact +---------- +Name of the button for adding a new contact for accessibility tech like screen readers + Dodaj kontakt + + + + + Preferences + Tooltip text for the button that launches program preferences window +---------- +Name of the button for launching the preferences window for accessibility tech like screen readres + Ustawienia + + + + Click to add contacts + Tooltip that displays on first launch indicating how to add a new contact + Kliknij, aby dodać do kontaktów + + + + Shows the add contact dialogue + Description of the 'Add Contact' button for accessibility tech like screen readers + + + + + Shows the preferences dialogue + Description of the 'Preferences' button for accessibility tech like screen readers + + + + + MainWindow + + + Version Seperator + + + + + Remove %1 + Usuń %1 + + + + Do you want to permanently remove %1? + Czy chcesz na stałe usunąć %1? + + + + Main Toolbar + Name of the main toolbar for accessibility tech like screen readers + + + + + Toolbar with connection status, add contact button, and preferences button + Description of the main toolbar for accessibility tech like screen readers + + + + + Contact pane + Name of the pane holding the user's contacts for accessibility tech like screen readers + + + + + MessageDelegate + + + %1 is offline + %1 nickname + %1 jest rozłączony + + + + File transfer file name + Description of the text displaying the filename of a file transfer, used by accessibility tech like screen readres + + + + + File transfer progress + Description of progress bar displaying the file transfer progress, used by accessibility tech like screen readers + + + + + File transfer status + Description of label displaying the current status of a file transfer, used by accessibility tech like screen readers + + + + + Download + Label for file transfer 'Download' button for accessibility tech like screen readers + + + + + Download file + Description of what the file transfer 'Download' button does for accessibility tech like screen readers + + + + + Cancel or reject + Label for file transfer 'Cancel' button for accessibility tech like screen readers + + + + + Cancels or rejects a file transfer + Description of what the file transfer 'Cancel' button does for accessibility tech like screen readers + + + + Copy ID + Text for context menu command to copy a ricochet contact id to clipboard + Skopiuj ID + + + Copy Link + Text for context menu command to copy a url to the clipboard + Skopiuj link + + + Open with Browser + Text for context menu command to open a url in a web browser + Otwórz w przeglądarce + + + Add as Contact + Dodaj jako kontakt + + + + Copy Message + Text for context menu command to copy an entire message to clipboard + Kopiuj wiadomość + + + + Copy Selection + Text for context menu command to copy selected text to clipboard + Kopiuj zaznaczenie + + + + MessageDialogWrapper + + + Remove %1 + A command to remove a contact from the contact list, %1 is the contact's nickname + Usuń %1 + + + + Do you want to permanently remove %1? + Confirmation of contact removal, %1 is the contact's nickname + Czy chcesz na stałe usunąć %1? + + + + This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. + Message describing what will happen when a contact is removed from you contact list + Ten kontakt nie będzie mógł więcej wysyłać do Ciebie wiadomości i zostanie poinformowany o usunięciu. Może wysłać Ci nową prośbę o połączenie. + + + + NetworkSetupWizard + + + This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. + A label with directions for when to use the 'Connect' button + Połączenie internetowe tego kompuera jest wolne od przeszkód. Chcę połączyć się bezpośrednio z siecią Tor. + + + + Connect + Label for button to connect to the Tor network + Połącz + + + + This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. + A label with directions for when to use the 'Configure' button + Połączenie internetowe tego komputera jest cenzurowane, filtrowane lub trasowane przez serwer proxy. Muszę skonfigurować ustawienia sieciowe. + + + + Configure + Label for button to configure the Tor daemon beore connecting to the Tor network + Konfiguruj + + + + OfflineStateItem + + + Configure + Button label + Konfiguruj + + + + Details + Button label + Szczegóły + + + + Connection failed + Label displayed when connecting to the Tor network fails + Połączenie nie powiodło się + + + + Connecting… + Label displayed when in process of connecting, … is ellipsis + Łączenie… + + + + OpenBrowserDialog + + <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. + Label displayed when user clicks on a link + <b>Uwaga!</b> Otwieranie linków za pomocą domyślnej przeglądarki spowoduje utratę bezpieczeństwa i anonimowości. Zamiast tego możesz <a href='.'>skopiować go do schowka</a> + + + Don't ask again for links from %1 + Nie pytaj ponownie dla linków od %1 + + + Don't ask again for any links (not recommended!) + Checkbox option text for when user clicks on a link + Nigdy nie pytaj ponownie (niezalecane!) + + + Open Browser + Label on button to open link in a web browser + Otwórz przeglądarkę + + + Cancel + Label on cancel button + Anuluj + + + + PreferencesDialog + + + Ricochet Preferences + Ustawienia Ricocheta + + + + Menu Tabs + Name of the tab bar for accessibility tech like screen readers + + + + + General + Title of the general settings tab + Główne + + + Language + Język + + + + Contacts + Title of the contacts list tab + Kontakty + + + + Tor + Title of the tor tab, contains tor settings and logs + Tor + + + + About + Title of the about tab, contains license information and ricochet version + O programie + + + + QCocoaMenuItem + + + Preference + Ustawienie + + + + StartupStatusPage + + + The Tor process was not started successfully. This is most likely an installation or system error. + Error status string displayed when tor daemon does not launch successfully + Tor nie został uruchomiony poprawnie. Najprawdopodobniej oznacza to błąd instalacji lub systemu. + + + + Quit + Button title to quit/terminate the program + Wyjdź + + + + TorBootstrapStatus + + + Connecting to the Tor network… + Text status displayed when tor daemon is connecting, … is ellipsis + Łączenie z siecią Tor… + + + + Connection status + Description of the connection status text, used by accessibility tech like screen readers + + + + + Connection progress + Description of the bootstrap progress bar, used by accessibility tech like screen readers + + + + + + Back + Label for button which moves us back to previous screen + Wróć + + + + Hide Details + Label for button which hides the tor logs + + + + + Show Details + Label for button which shows the tor logs + + + + + Hides the tor progress log + Description of hide details button, used by accessibility tech like screen readers + + + + + Shows the tor progress log + Description of show details button, used by accessibility tech like screen readers + + + + Hide details + Ukryj szczegóły + + + Show details + Pokaż szczegóły + + + + Done + Label for button which closes the tor connection window + Zrobione + + + + TorConfigurationPage + + + Does this computer need a proxy to access the internet? + Czy ten komputer potrzebuje serwera proxy, aby łączyć się z Internetem? + + + + Proxy type: + Typ proxy: + + + + + None + Brak + + + + If you need a proxy to access the internet, select one from this list. + Description used by accessibility tech, such as screen readers + + + + + Address: + Label indicating the textbox to place a proxy IP or URL + Adres: + + + + IP address or hostname + Placeholder text of text box expecting an IP or URL for proxy + Adres IP lub nazwa hosta + + + + Enter the IP address or hostname of the proxy you wish to connect to + Description of what to enter into the IP textbox, used by accessibility tech such as screen readers + + + + + Port: + Label indicating the textbox to place a proxy port + Port: + + + + Port + Name of the port label, used by accessibility tech such as screen readers + + + + + Enter the port of the proxy you wish to connect to + Description of what to enter into the Port textbox, used by accessibility tech such as screen readers + + + + + Username: + Label indicating the textbox to place the proxy username + Nazwa użytkownika: + + + + + Optional + Textbox placeholder text indicating the field is not required + Opcjonalny + + + + Username + Name of the username label, used by accessibility tech such as screen readers + + + + + If required, enter the username for the proxy you wish to connect to + Description to enter into the Username textbox, used by accessibility tech such as screen readers + + + + + Password: + Label indicating the textbox to place the proxy password + Hasło: + + + + Password + Name of the password label, used by accessibility tech such as screen readers + + + + + If required, enter the password for the proxy you wish to connect to + Description to enter into the Password textbox, used by accessibility tech such as screen readers + + + + + Does this computer's Internet connection go through a firewall that only allows connections to certain ports? + Description for the purpose of the Allowed Ports textbox + Czy połączenie internetowe tego komputera jest kierowane przez firewall, który pozwala na łączenie się tylko z wybranymi portami? + + + + Allowed ports: + Label indicating the textbox to place the allowed ports + Dozwolone porty: + + + + Example: 80,443 + Textbox showing an example entry for the firewall allowed ports entry + Na przykład: 80,443 + + + + Allowed ports + Name of the allowed ports label, used by accessibility tech such as screen readers + + + + + If this computer's Internet connection is censored, you will need to obtain and use bridge relays. + Jeżeli połączenie internetowe tego komputera jest cenzurowane, będziesz musiał uzyskać dostęp do węzła pośredniego (<i>bridge relay</i>). + + + + + Enter one or more bridge relays (one per line): + Podaj jeden lub więcej węzłów pośrednich (jeden w linii): + + + + Back + Button label for going back to previous screen + Wróć + + + + Connect + Button label for connecting to tor + Połącz + + + + TorLogDisplay + + + Tor log + Name of the text field containg the tor logs, used by accessibility tech such as screen readers + + + + + TorPreferences + + + Running: + Display label that beside it indicates whether tor is running + Uruchomione: + + + + Running + + + + + Whether tor is running + Description of the value of this label, used by acccessibility tech like screen readers + + + + + + Yes + Tak + + + + + No + Nie + + + External + Zewnętrzny + + + + Control connected: + Display label that beside it indicates whether ricochet is connected to tor's control port + Połączony z portem kontrolnym: + + + + Control connected + + + + + Whether tor control connected + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Circuits established: + Obwody utworzone: + + + + Circuits established + + + + + Whether circuits established + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Hidden service: + Ukryte usługi: + + + + Hidden service + + + + + Online + Połączony + + + + Offline + Rozłączony + + + + Whether a hidden service is up or not + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Version: + Wersja: + + + + Version + + + + + Error: <b>%1</b> + %1 is error message + Błąd: <b>%1</b> + + + + Configure + Konfiguruj + + + + TorStateWidget + + + + Connection failed + Połączenie nie powiodło się + + + + + Connecting… + … is ellipsis + Łączenie… + + + + Connecting… (%1%) + %1 is progress percentage, e.g. 100 + Łączenie… (%1%) + + + + Online + Połączony + + + + Connected + Połączony + + + + Tor status + + + + + shims::ConversationModel + + + Pending + + + + + Accepted + Zaakceptowany + + + + Rejected + + + + + Cancelled + + + + + Complete + + + + + Unkown Failure + + + + + Bad File Hash + + + + + Network Error + + + + + File System Error + + + + + Invalid + + + + + + Open File + + + + + + + + me + + + + + Save File + + + + diff --git a/src/ricochet-refresh/translation/ricochet_pt_BR.ts b/src/ricochet-refresh/translation/ricochet_pt_BR.ts new file mode 100644 index 00000000..087cb1ec --- /dev/null +++ b/src/ricochet-refresh/translation/ricochet_pt_BR.ts @@ -0,0 +1,1257 @@ + + + + + AboutPreferences + + Ricochet %1 + %1 version, e.g. 1.0.0 + Ricochet %1 + + + + Ricochet-Refresh %1 + %1 version, e.g. 1.0.0 + + + + + Ricochet Refresh version %1 + provides a readable interpretation of the Ricochet-Refresh version number for accessibility tech like screen readers + + + + + The license of Ricochet Refresh and its dependencies + + + + + License + + + + + About + Sobre + + + + About page, contains license and version information + summary of the window's contents for accessibility tech like screen readers + + + + + Current Ricochet-Refresh version + + + + + AddContactDialog + + + Share your Ricochet ID to allow connection requests + tells the user the purpose of their Ricochet ID, which is basically a username + Compartilhe seu ID Ricochet para permitir pedidos de conexão + + + + Cancel + label for button which dismisses a dialog + Cancelar + + + + Closes the contact add window + description for 'Cancel' button accessibility tech like screen readers + + + + + Add + button label to finish adding a contact/friend + Adicionar + + + + Adds the contact to your contact list + description for 'Add' button for accessibility tech like screen readres + + + + + ChatPage + + + Message area + label for accessibility tech like screen readers + + + + + Write the message to be sent here. Press enter to send + description of the text area used to send messages for accessibility tech like screen readers + + + + + ContactActions + + + Warning + + + + + Could not successfully export conversation + + + + + Open Window + Context menu command to open the chat screen in a separate window + Abrir Janela + + + + Details... + Context menu command to open a window showing the selected contact's details + Detalhes... + + + + Rename + Context menu command to rename the selected contact + Renomear + + + + Send File... + Context menu command to initiate a file transfer, opens a system file dialog + + + + + Export Conversation + Context menu command to initiate a chat log export, opens a system file dialog to export to + + + + + Remove + Context menu command to remove a contact from the contact list + Remover + + + + Contact options + Description of the items in the context menu for accessibility tech like screen readers + + + + + ContactIDField + + + <b>%1</b> is already your contact + Error message showed when user attempts to add a contact already in their contact list + <b>%1</b> já é seu contato + + + + This ID is invalid + Error message showed when the id doesn't comply with spec https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt + + + + + You can't add yourself as a contact + Error message showed when user attempts to add themselves as a contact in their contact list + Você não pode adicionar a si mesmo como um contato + + + + Enter an ID starting with <b>ricochet:</b> + Error message showed when the provided ricochet id is invalid + Entre uma ID começando com <b>ricochet:</b> + + + + Copied to clipboard + Message displayed when text is copied to the user's clipboard + Copiado para a área de transferência + + + + Copy + Text displayed on a button used to copy somethign to the user's clipboard + Copiar + + + + Copies the ricochet id to the clipboard + Text description of ricochet id copy button for accessibility tech like screen readers + + + + + ContactList + + + Online + Section header in the contact list for users which are online + Online + + + + Offline + Section header in the contact list for users which are offline + Offline + + + + Requests + Section header in the contact list for users requesting to be added to the user's contact list + Pedidos + + + + Rejected + Section header in the contact list for users that have rejected the user's request to be added to their contact list + Rejeitado + + + + Status for the given contacts + Description of the section header for accessibility tech like screen readers + + + + Outdated + Desatualizado + + + + ContactPreferences + + Date added: + Adicionado em: + + + Last seen: + Visto pela última vez: + + + Request: + Pedido: + + + Pending connection + Conexão pendente + + + Delivered + Entregue + + + Accepted + Aceito + + + Error + Erro + + + Rejected + Rejeitado + + + %1 (Connected) + %1 status, e.g. "Accepted" + %1 (Conectado) + + + Response: + Resposta: + + + + Contact list + Description of the list of contacts for accessibility tech like screen readers + + + + + Contact ID for + Description of text box containing a contact's contact id for accessibility tech like screen readers + + + + + + Unknown user + A placeholder name for a contact whose name we do not know + + + + + Rename + Label for button which allows renaming of a contact + Renomear + + + + Renames this contact + Description of button which renames a contact for accessibility tech like screen readers + + + + + Remove + Label for button which removes a contact from the contact list + Remover + + + + Removes this contact + Description of button which removes a user from the contact list for accessibility tech like screen readers + + + + + Preferences for contact + Description of the contents of the 'Contacts' window for accessibility tech like screen readers + + + + + Contact Preferences Window + Name of the contact preferences window for accessibility tech like screen readers + + + + + A list of all your contacts, with their ricochet IDs, and options such as renaming and removing + Description of what user can do in the contact preferences window for accessibility tech like screen readers + + + + + ContactRequestDialog + + + Someone new is asking to connect to you + Descriptive text that is displayed in a popup window when a user tries to add you as a contact + Alguém novo está pedindo para se conectar com você + + + + Reject + Label for button which rejects a contact request when pressed + Rejeitar + + + + Rejects the incoming contact request + Description of what 'Reject' button does for accessibility tech like screen readers + + + + + Accept + Label for button which accepts a contact request when pressed + Aceitar + + + + Accepts the incoming contact request + Description of what 'Accept' button does for accessibility tech like screen readers + + + + + ContactRequestFields + + + ID: + Label for the contact id text box in the 'add new contact' window + ID: + + + + Name: + Label for the contact nickname text box in the 'add new contact' window + Nome: + + + + Field for the contact's nickname + Description of textbox for setting a contact's nickname for accessibility tech like screen readers + + + + + Message: + Label for the contact greeting message text box in the 'add new contact' window + Mensagem: + + + + Field for the contact's greeting message + Description of textbox for setting a new contact's initial greeting message for accessibility tech like screen readers + + + + + GeneralPreferences + + + Use a single window for conversations + Text description of an option to use one single program window for the contact list and the chats + Usar somente uma janela para conversas + + + Open links in default browser without prompting + Text description of an option to open URLs in default web browser when clicked + Abrir links no navegador padrão sem pedir confirmação + + + + Play audio notifications + Text description of an option to play audio notifications when contacts log in, log out, and send messages + Tocar notificações de áudio + + + + + Volume + Label for a slider used to adjust audio notification volume +---------- +Name of the slider used to adjust audio notification volume for accessibility tech like screen readers + Volume + + + + + Language + Label for combobox where users can specify the UI language +---------- +Name of the combobox used to select UI langauge for accessibility tech like screen readers + Idioma + + + + Restart Ricochet to apply changes + Reiniciar Ricochet para aplicar mudanças + + + + What language ricochet will use + Description of what the language combox is for for accessibility tech like screen readers + + + + + LanguagePreferences + + Select Language + Selecionar Linguagem + + + Restart Ricochet to apply changes + Reiniciar Ricochet para aplicar mudanças + + + + LanguagesModel + + + System default + Padrão do sistema + + + + Main + + + Ricochet Error + Erro do Ricochet + + + + MainToolBar + + + + Add Contact + Tooltip text for the button that launches the dialog box for adding a new contact +---------- +Name of the button for adding a new contact for accessibility tech like screen readers + Adicionar Contato + + + + + Preferences + Tooltip text for the button that launches program preferences window +---------- +Name of the button for launching the preferences window for accessibility tech like screen readres + Preferências + + + + Click to add contacts + Tooltip that displays on first launch indicating how to add a new contact + Clique para adicionar contatos + + + + Shows the add contact dialogue + Description of the 'Add Contact' button for accessibility tech like screen readers + + + + + Shows the preferences dialogue + Description of the 'Preferences' button for accessibility tech like screen readers + + + + + MainWindow + + + Version Seperator + + + + + Remove %1 + Remover %1 + + + + Do you want to permanently remove %1? + Você deseja remover %1 permanentemente? + + + + Main Toolbar + Name of the main toolbar for accessibility tech like screen readers + + + + + Toolbar with connection status, add contact button, and preferences button + Description of the main toolbar for accessibility tech like screen readers + + + + + Contact pane + Name of the pane holding the user's contacts for accessibility tech like screen readers + + + + + MessageDelegate + + + %1 is offline + %1 nickname + %1 está offline + + + + File transfer file name + Description of the text displaying the filename of a file transfer, used by accessibility tech like screen readres + + + + + File transfer progress + Description of progress bar displaying the file transfer progress, used by accessibility tech like screen readers + + + + + File transfer status + Description of label displaying the current status of a file transfer, used by accessibility tech like screen readers + + + + + Download + Label for file transfer 'Download' button for accessibility tech like screen readers + + + + + Download file + Description of what the file transfer 'Download' button does for accessibility tech like screen readers + + + + + Cancel or reject + Label for file transfer 'Cancel' button for accessibility tech like screen readers + + + + + Cancels or rejects a file transfer + Description of what the file transfer 'Cancel' button does for accessibility tech like screen readers + + + + Copy ID + Text for context menu command to copy a ricochet contact id to clipboard + Copiar ID + + + Copy Link + Text for context menu command to copy a url to the clipboard + Copiar Link + + + Open with Browser + Text for context menu command to open a url in a web browser + Abrir no Navegador + + + Add as Contact + Adicionar como Contato + + + + Copy Message + Text for context menu command to copy an entire message to clipboard + Copiar Mensagem + + + + Copy Selection + Text for context menu command to copy selected text to clipboard + Copiar Seleção + + + + MessageDialogWrapper + + + Remove %1 + A command to remove a contact from the contact list, %1 is the contact's nickname + Remover %1 + + + + Do you want to permanently remove %1? + Confirmation of contact removal, %1 is the contact's nickname + Você deseja remover %1 permanentemente? + + + + This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. + Message describing what will happen when a contact is removed from you contact list + Este contato não poderá mais te enviar mensagens, e será notificado sobre a remoção. Ele pode escolher enviar novo pedido de conexão. + + + + NetworkSetupWizard + + + This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. + A label with directions for when to use the 'Connect' button + A conexão à internet deste computador está livre de obstáculos. Quero me conectar diretamente à rede Tor. + + + + Connect + Label for button to connect to the Tor network + Conectar + + + + This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. + A label with directions for when to use the 'Configure' button + A conexão à internet deste computador é censurada, filtrada, ou passa por proxy. Preciso configurar a rede. + + + + Configure + Label for button to configure the Tor daemon beore connecting to the Tor network + Configurar + + + + OfflineStateItem + + + Configure + Button label + Configurar + + + + Details + Button label + Detalhes + + + + Connection failed + Label displayed when connecting to the Tor network fails + A conexão falhou + + + + Connecting… + Label displayed when in process of connecting, … is ellipsis + Conectando... + + + + OpenBrowserDialog + + <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. + Label displayed when user clicks on a link + <b>Atenção!</b> Abrir links com o seu navegador pode danificar sua segurança e anonimidade.<br><br>Ao invés disso, você pode <a href='.'>copiar para a área de transferência</a>. + + + Don't ask again for links from %1 + Não perguntar de novo para links de %1 + + + Don't ask again for any links (not recommended!) + Checkbox option text for when user clicks on a link + Não perguntar de novo para quaisquer links (não recomendado!) + + + Open Browser + Label on button to open link in a web browser + Abrir Navegador + + + Cancel + Label on cancel button + Cancelar + + + + PreferencesDialog + + + Ricochet Preferences + Preferências do Ricochet + + + + Menu Tabs + Name of the tab bar for accessibility tech like screen readers + + + + + General + Title of the general settings tab + Geral + + + Language + Idioma + + + + Contacts + Title of the contacts list tab + Contatos + + + + Tor + Title of the tor tab, contains tor settings and logs + Tor + + + + About + Title of the about tab, contains license information and ricochet version + Sobre + + + + QCocoaMenuItem + + + Preference + Preferência + + + + StartupStatusPage + + + The Tor process was not started successfully. This is most likely an installation or system error. + Error status string displayed when tor daemon does not launch successfully + O processo do Tor não foi iniciado com sucesso. Muito provavelmente isso é um erro de instalação ou de sistema. + + + + Quit + Button title to quit/terminate the program + Sair + + + + TorBootstrapStatus + + + Connecting to the Tor network… + Text status displayed when tor daemon is connecting, … is ellipsis + Conectando à rede Tor... + + + + Connection status + Description of the connection status text, used by accessibility tech like screen readers + + + + + Connection progress + Description of the bootstrap progress bar, used by accessibility tech like screen readers + + + + + + Back + Label for button which moves us back to previous screen + Voltar + + + + Hide Details + Label for button which hides the tor logs + + + + + Show Details + Label for button which shows the tor logs + + + + + Hides the tor progress log + Description of hide details button, used by accessibility tech like screen readers + + + + + Shows the tor progress log + Description of show details button, used by accessibility tech like screen readers + + + + Hide details + Esconder detalhes + + + Show details + Mostrar detalhes + + + + Done + Label for button which closes the tor connection window + Feito + + + + TorConfigurationPage + + + Does this computer need a proxy to access the internet? + Este computador precisa de um proxy para acessar a internet? + + + + Proxy type: + Tipo de proxy: + + + + + None + Nenhum + + + + If you need a proxy to access the internet, select one from this list. + Description used by accessibility tech, such as screen readers + + + + + Address: + Label indicating the textbox to place a proxy IP or URL + Endereço: + + + + IP address or hostname + Placeholder text of text box expecting an IP or URL for proxy + Endereço IP ou nome do host + + + + Enter the IP address or hostname of the proxy you wish to connect to + Description of what to enter into the IP textbox, used by accessibility tech such as screen readers + + + + + Port: + Label indicating the textbox to place a proxy port + Porta: + + + + Port + Name of the port label, used by accessibility tech such as screen readers + + + + + Enter the port of the proxy you wish to connect to + Description of what to enter into the Port textbox, used by accessibility tech such as screen readers + + + + + Username: + Label indicating the textbox to place the proxy username + Usuário: + + + + + Optional + Textbox placeholder text indicating the field is not required + Opcional + + + + Username + Name of the username label, used by accessibility tech such as screen readers + + + + + If required, enter the username for the proxy you wish to connect to + Description to enter into the Username textbox, used by accessibility tech such as screen readers + + + + + Password: + Label indicating the textbox to place the proxy password + Senha: + + + + Password + Name of the password label, used by accessibility tech such as screen readers + + + + + If required, enter the password for the proxy you wish to connect to + Description to enter into the Password textbox, used by accessibility tech such as screen readers + + + + + Does this computer's Internet connection go through a firewall that only allows connections to certain ports? + Description for the purpose of the Allowed Ports textbox + A conexão à internet deste computador passa por um firewall que só permite conexões a certas portas? + + + + Allowed ports: + Label indicating the textbox to place the allowed ports + Portas permitidas: + + + + Example: 80,443 + Textbox showing an example entry for the firewall allowed ports entry + Exemplo: 80,443 + + + + Allowed ports + Name of the allowed ports label, used by accessibility tech such as screen readers + + + + + If this computer's Internet connection is censored, you will need to obtain and use bridge relays. + Se a conexão à internet deste computador é censurada, você precisará obter e usar bridge relays. + + + + + Enter one or more bridge relays (one per line): + Entre um ou mais bridge relays (um por linha): + + + + Back + Button label for going back to previous screen + Voltar + + + + Connect + Button label for connecting to tor + Conectar + + + + TorLogDisplay + + + Tor log + Name of the text field containg the tor logs, used by accessibility tech such as screen readers + + + + + TorPreferences + + + Running: + Display label that beside it indicates whether tor is running + Executando: + + + + Running + + + + + Whether tor is running + Description of the value of this label, used by acccessibility tech like screen readers + + + + + + Yes + Sim + + + + + No + Não + + + External + Externo + + + + Control connected: + Display label that beside it indicates whether ricochet is connected to tor's control port + Controle conectado: + + + + Control connected + + + + + Whether tor control connected + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Circuits established: + Circuitos estabelecidos: + + + + Circuits established + + + + + Whether circuits established + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Hidden service: + Serviço escondido: + + + + Hidden service + + + + + Online + Online + + + + Offline + Offline + + + + Whether a hidden service is up or not + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Version: + Versão: + + + + Version + + + + + Error: <b>%1</b> + %1 is error message + Erro: <b>%1</b> + + + + Configure + Configurar + + + + TorStateWidget + + + + Connection failed + A conexão falhou + + + + + Connecting… + … is ellipsis + Conectando... + + + + Connecting… (%1%) + %1 is progress percentage, e.g. 100 + Conectando... (%1) + + + + Online + Online + + + + Connected + Conectado + + + + Tor status + + + + + shims::ConversationModel + + + Pending + + + + + Accepted + Aceito + + + + Rejected + Rejeitado + + + + Cancelled + + + + + Complete + + + + + Unkown Failure + + + + + Bad File Hash + + + + + Network Error + + + + + File System Error + + + + + Invalid + + + + + + Open File + + + + + + + + me + + + + + Save File + + + + diff --git a/src/ricochet-refresh/translation/ricochet_pt_PT.ts b/src/ricochet-refresh/translation/ricochet_pt_PT.ts new file mode 100644 index 00000000..f0ff2e20 --- /dev/null +++ b/src/ricochet-refresh/translation/ricochet_pt_PT.ts @@ -0,0 +1,1257 @@ + + + + + AboutPreferences + + Ricochet %1 + %1 version, e.g. 1.0.0 + Ricochet %1 + + + + Ricochet-Refresh %1 + %1 version, e.g. 1.0.0 + + + + + Ricochet Refresh version %1 + provides a readable interpretation of the Ricochet-Refresh version number for accessibility tech like screen readers + + + + + The license of Ricochet Refresh and its dependencies + + + + + License + + + + + About + Sobre + + + + About page, contains license and version information + summary of the window's contents for accessibility tech like screen readers + + + + + Current Ricochet-Refresh version + + + + + AddContactDialog + + + Share your Ricochet ID to allow connection requests + tells the user the purpose of their Ricochet ID, which is basically a username + Partilhe o seu ID Ricochet para permitir pedidos de ligação + + + + Cancel + label for button which dismisses a dialog + Cancelar + + + + Closes the contact add window + description for 'Cancel' button accessibility tech like screen readers + + + + + Add + button label to finish adding a contact/friend + Adicionar + + + + Adds the contact to your contact list + description for 'Add' button for accessibility tech like screen readres + + + + + ChatPage + + + Message area + label for accessibility tech like screen readers + + + + + Write the message to be sent here. Press enter to send + description of the text area used to send messages for accessibility tech like screen readers + + + + + ContactActions + + + Warning + + + + + Could not successfully export conversation + + + + + Open Window + Context menu command to open the chat screen in a separate window + Abrir Janela + + + + Details... + Context menu command to open a window showing the selected contact's details + Detalhes... + + + + Rename + Context menu command to rename the selected contact + Mudar o nome + + + + Send File... + Context menu command to initiate a file transfer, opens a system file dialog + + + + + Export Conversation + Context menu command to initiate a chat log export, opens a system file dialog to export to + + + + + Remove + Context menu command to remove a contact from the contact list + Remover + + + + Contact options + Description of the items in the context menu for accessibility tech like screen readers + + + + + ContactIDField + + + <b>%1</b> is already your contact + Error message showed when user attempts to add a contact already in their contact list + <b>%1</b> já é um contacto seu + + + + This ID is invalid + Error message showed when the id doesn't comply with spec https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt + + + + + You can't add yourself as a contact + Error message showed when user attempts to add themselves as a contact in their contact list + Não pode adicionar-se a si mesmo como contacto + + + + Enter an ID starting with <b>ricochet:</b> + Error message showed when the provided ricochet id is invalid + Insira um ID que comece com <b>ricochet:</b> + + + + Copied to clipboard + Message displayed when text is copied to the user's clipboard + Copiado para a área de transferência + + + + Copy + Text displayed on a button used to copy somethign to the user's clipboard + Copiar + + + + Copies the ricochet id to the clipboard + Text description of ricochet id copy button for accessibility tech like screen readers + + + + + ContactList + + + Online + Section header in the contact list for users which are online + Online + + + + Offline + Section header in the contact list for users which are offline + Offline + + + + Requests + Section header in the contact list for users requesting to be added to the user's contact list + Pedidos + + + + Rejected + Section header in the contact list for users that have rejected the user's request to be added to their contact list + Rejeitados + + + + Status for the given contacts + Description of the section header for accessibility tech like screen readers + + + + Outdated + Desatualizado + + + + ContactPreferences + + Date added: + Adicionado a: + + + Last seen: + Visto pela última vez: + + + Request: + Pedido: + + + Pending connection + Ligação pendente + + + Delivered + Enviado + + + Accepted + Aceite + + + Error + Erro + + + Rejected + Rejeitado + + + %1 (Connected) + %1 status, e.g. "Accepted" + %1 (Ligado) + + + Response: + Resposta: + + + + Contact list + Description of the list of contacts for accessibility tech like screen readers + + + + + Contact ID for + Description of text box containing a contact's contact id for accessibility tech like screen readers + + + + + + Unknown user + A placeholder name for a contact whose name we do not know + + + + + Rename + Label for button which allows renaming of a contact + Mudar o nome + + + + Renames this contact + Description of button which renames a contact for accessibility tech like screen readers + + + + + Remove + Label for button which removes a contact from the contact list + Remover + + + + Removes this contact + Description of button which removes a user from the contact list for accessibility tech like screen readers + + + + + Preferences for contact + Description of the contents of the 'Contacts' window for accessibility tech like screen readers + + + + + Contact Preferences Window + Name of the contact preferences window for accessibility tech like screen readers + + + + + A list of all your contacts, with their ricochet IDs, and options such as renaming and removing + Description of what user can do in the contact preferences window for accessibility tech like screen readers + + + + + ContactRequestDialog + + + Someone new is asking to connect to you + Descriptive text that is displayed in a popup window when a user tries to add you as a contact + Alguém novo está a pedir para se ligar a si + + + + Reject + Label for button which rejects a contact request when pressed + Rejeitar + + + + Rejects the incoming contact request + Description of what 'Reject' button does for accessibility tech like screen readers + + + + + Accept + Label for button which accepts a contact request when pressed + Aceitar + + + + Accepts the incoming contact request + Description of what 'Accept' button does for accessibility tech like screen readers + + + + + ContactRequestFields + + + ID: + Label for the contact id text box in the 'add new contact' window + ID: + + + + Name: + Label for the contact nickname text box in the 'add new contact' window + Nome: + + + + Field for the contact's nickname + Description of textbox for setting a contact's nickname for accessibility tech like screen readers + + + + + Message: + Label for the contact greeting message text box in the 'add new contact' window + Mensagem: + + + + Field for the contact's greeting message + Description of textbox for setting a new contact's initial greeting message for accessibility tech like screen readers + + + + + GeneralPreferences + + + Use a single window for conversations + Text description of an option to use one single program window for the contact list and the chats + Utilizar uma única janela para conversas + + + Open links in default browser without prompting + Text description of an option to open URLs in default web browser when clicked + Abrir links no navegador padrão sem pedir confirmação + + + + Play audio notifications + Text description of an option to play audio notifications when contacts log in, log out, and send messages + Ativar notificações de áudio + + + + + Volume + Label for a slider used to adjust audio notification volume +---------- +Name of the slider used to adjust audio notification volume for accessibility tech like screen readers + Volume + + + + + Language + Label for combobox where users can specify the UI language +---------- +Name of the combobox used to select UI langauge for accessibility tech like screen readers + Idioma + + + + Restart Ricochet to apply changes + Reiniciar o Ricochet para aplicar as alterações + + + + What language ricochet will use + Description of what the language combox is for for accessibility tech like screen readers + + + + + LanguagePreferences + + Select Language + Selecionar Idioma + + + Restart Ricochet to apply changes + Reiniciar o Ricochet para aplicar as alterações + + + + LanguagesModel + + + System default + Padrão do sistema + + + + Main + + + Ricochet Error + Erro do Ricochet + + + + MainToolBar + + + + Add Contact + Tooltip text for the button that launches the dialog box for adding a new contact +---------- +Name of the button for adding a new contact for accessibility tech like screen readers + Adicionar Contacto + + + + + Preferences + Tooltip text for the button that launches program preferences window +---------- +Name of the button for launching the preferences window for accessibility tech like screen readres + Preferências + + + + Click to add contacts + Tooltip that displays on first launch indicating how to add a new contact + Clique para adicionar contactos + + + + Shows the add contact dialogue + Description of the 'Add Contact' button for accessibility tech like screen readers + + + + + Shows the preferences dialogue + Description of the 'Preferences' button for accessibility tech like screen readers + + + + + MainWindow + + + Version Seperator + + + + + Remove %1 + Remover %1 + + + + Do you want to permanently remove %1? + Deseja remover %1 permanentemente? + + + + Main Toolbar + Name of the main toolbar for accessibility tech like screen readers + + + + + Toolbar with connection status, add contact button, and preferences button + Description of the main toolbar for accessibility tech like screen readers + + + + + Contact pane + Name of the pane holding the user's contacts for accessibility tech like screen readers + + + + + MessageDelegate + + + %1 is offline + %1 nickname + %1 está offline + + + + File transfer file name + Description of the text displaying the filename of a file transfer, used by accessibility tech like screen readres + + + + + File transfer progress + Description of progress bar displaying the file transfer progress, used by accessibility tech like screen readers + + + + + File transfer status + Description of label displaying the current status of a file transfer, used by accessibility tech like screen readers + + + + + Download + Label for file transfer 'Download' button for accessibility tech like screen readers + + + + + Download file + Description of what the file transfer 'Download' button does for accessibility tech like screen readers + + + + + Cancel or reject + Label for file transfer 'Cancel' button for accessibility tech like screen readers + + + + + Cancels or rejects a file transfer + Description of what the file transfer 'Cancel' button does for accessibility tech like screen readers + + + + Copy ID + Text for context menu command to copy a ricochet contact id to clipboard + Copiar ID + + + Copy Link + Text for context menu command to copy a url to the clipboard + Copiar Link + + + Open with Browser + Text for context menu command to open a url in a web browser + Abrir com o Navegador + + + Add as Contact + Adicionar como Contacto + + + + Copy Message + Text for context menu command to copy an entire message to clipboard + Copiar Mensagem + + + + Copy Selection + Text for context menu command to copy selected text to clipboard + Copiar Seleção + + + + MessageDialogWrapper + + + Remove %1 + A command to remove a contact from the contact list, %1 is the contact's nickname + Remover %1 + + + + Do you want to permanently remove %1? + Confirmation of contact removal, %1 is the contact's nickname + Deseja remover %1 permanentemente? + + + + This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. + Message describing what will happen when a contact is removed from you contact list + Este contacto deixará de lhe poder enviar mensagens, e será ainda notificado sobre a remoção. No entanto, este poderá enviar um novo pedido de ligação. + + + + NetworkSetupWizard + + + This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. + A label with directions for when to use the 'Connect' button + A ligação à Internet deste computador está livre de obstáculos. Pretendo ligar-me diretamente à rede Tor. + + + + Connect + Label for button to connect to the Tor network + Ligar + + + + This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. + A label with directions for when to use the 'Configure' button + A ligação à Internet deste computador é censurada, filtrada, ou passa por um proxy. Necessito de configurar as definições da rede. + + + + Configure + Label for button to configure the Tor daemon beore connecting to the Tor network + Configurar + + + + OfflineStateItem + + + Configure + Button label + Configurar + + + + Details + Button label + Detalhes + + + + Connection failed + Label displayed when connecting to the Tor network fails + Ligação falhou + + + + Connecting… + Label displayed when in process of connecting, … is ellipsis + Ligando... + + + + OpenBrowserDialog + + <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. + Label displayed when user clicks on a link + <b>Atenção!</b> Abrir links com o seu navegador padrão irá diminuir a sua segurança e anonimidade.<br><br>Em vez disso, poderá <a href='.'>copiar para a área de transferência</a>. + + + Don't ask again for links from %1 + Não perguntar de novo para links de %1 + + + Don't ask again for any links (not recommended!) + Checkbox option text for when user clicks on a link + Não perguntar de novo para quaisquer links (não recomendado!) + + + Open Browser + Label on button to open link in a web browser + Abrir Navegador + + + Cancel + Label on cancel button + Cancelar + + + + PreferencesDialog + + + Ricochet Preferences + Preferências do Ricochet + + + + Menu Tabs + Name of the tab bar for accessibility tech like screen readers + + + + + General + Title of the general settings tab + Geral + + + Language + Idioma + + + + Contacts + Title of the contacts list tab + Contactos + + + + Tor + Title of the tor tab, contains tor settings and logs + Tor + + + + About + Title of the about tab, contains license information and ricochet version + Sobre + + + + QCocoaMenuItem + + + Preference + Preferência + + + + StartupStatusPage + + + The Tor process was not started successfully. This is most likely an installation or system error. + Error status string displayed when tor daemon does not launch successfully + O processo do Tor não foi iniciado com sucesso. Tal deve-se, provavelmente, a um erro de instalação ou de sistema. + + + + Quit + Button title to quit/terminate the program + Sair + + + + TorBootstrapStatus + + + Connecting to the Tor network… + Text status displayed when tor daemon is connecting, … is ellipsis + A ligar à rede Tor... + + + + Connection status + Description of the connection status text, used by accessibility tech like screen readers + + + + + Connection progress + Description of the bootstrap progress bar, used by accessibility tech like screen readers + + + + + + Back + Label for button which moves us back to previous screen + Voltar + + + + Hide Details + Label for button which hides the tor logs + + + + + Show Details + Label for button which shows the tor logs + + + + + Hides the tor progress log + Description of hide details button, used by accessibility tech like screen readers + + + + + Shows the tor progress log + Description of show details button, used by accessibility tech like screen readers + + + + Hide details + Esconder detalhes + + + Show details + Mostrar detalhes + + + + Done + Label for button which closes the tor connection window + Terminado + + + + TorConfigurationPage + + + Does this computer need a proxy to access the internet? + Este computador necessita de um proxy para aceder à internet? + + + + Proxy type: + Tipo de proxy: + + + + + None + Nenhum + + + + If you need a proxy to access the internet, select one from this list. + Description used by accessibility tech, such as screen readers + + + + + Address: + Label indicating the textbox to place a proxy IP or URL + Endereço: + + + + IP address or hostname + Placeholder text of text box expecting an IP or URL for proxy + Endereço de IP ou nome do host + + + + Enter the IP address or hostname of the proxy you wish to connect to + Description of what to enter into the IP textbox, used by accessibility tech such as screen readers + + + + + Port: + Label indicating the textbox to place a proxy port + Porta: + + + + Port + Name of the port label, used by accessibility tech such as screen readers + + + + + Enter the port of the proxy you wish to connect to + Description of what to enter into the Port textbox, used by accessibility tech such as screen readers + + + + + Username: + Label indicating the textbox to place the proxy username + Nome de utilizador: + + + + + Optional + Textbox placeholder text indicating the field is not required + Opcional + + + + Username + Name of the username label, used by accessibility tech such as screen readers + + + + + If required, enter the username for the proxy you wish to connect to + Description to enter into the Username textbox, used by accessibility tech such as screen readers + + + + + Password: + Label indicating the textbox to place the proxy password + Palavra-passe: + + + + Password + Name of the password label, used by accessibility tech such as screen readers + + + + + If required, enter the password for the proxy you wish to connect to + Description to enter into the Password textbox, used by accessibility tech such as screen readers + + + + + Does this computer's Internet connection go through a firewall that only allows connections to certain ports? + Description for the purpose of the Allowed Ports textbox + A ligação à Internet deste computador passa por uma firewall que apenas permite ligações por certas portas? + + + + Allowed ports: + Label indicating the textbox to place the allowed ports + Portas permitidas: + + + + Example: 80,443 + Textbox showing an example entry for the firewall allowed ports entry + Exemplo: 80,443 + + + + Allowed ports + Name of the allowed ports label, used by accessibility tech such as screen readers + + + + + If this computer's Internet connection is censored, you will need to obtain and use bridge relays. + Se a ligação à Internet deste computador é censurada, necessitará de obter e utilizar bridge relays. + + + + + Enter one or more bridge relays (one per line): + Insira um ou mais bridge relays (um por linha): + + + + Back + Button label for going back to previous screen + Voltar + + + + Connect + Button label for connecting to tor + Ligar + + + + TorLogDisplay + + + Tor log + Name of the text field containg the tor logs, used by accessibility tech such as screen readers + + + + + TorPreferences + + + Running: + Display label that beside it indicates whether tor is running + A correr: + + + + Running + + + + + Whether tor is running + Description of the value of this label, used by acccessibility tech like screen readers + + + + + + Yes + Sim + + + + + No + Não + + + External + Externo + + + + Control connected: + Display label that beside it indicates whether ricochet is connected to tor's control port + Controlo ligado: + + + + Control connected + + + + + Whether tor control connected + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Circuits established: + Circuitos estabelecidos: + + + + Circuits established + + + + + Whether circuits established + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Hidden service: + Serviço oculto: + + + + Hidden service + + + + + Online + Online + + + + Offline + Offline + + + + Whether a hidden service is up or not + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Version: + Versão: + + + + Version + + + + + Error: <b>%1</b> + %1 is error message + Erro: <b>%1</b> + + + + Configure + Configurar + + + + TorStateWidget + + + + Connection failed + Ligação falhou + + + + + Connecting… + … is ellipsis + Ligando... + + + + Connecting… (%1%) + %1 is progress percentage, e.g. 100 + Ligando... (%1%) + + + + Online + Online + + + + Connected + Ligado + + + + Tor status + + + + + shims::ConversationModel + + + Pending + + + + + Accepted + Aceite + + + + Rejected + + + + + Cancelled + + + + + Complete + + + + + Unkown Failure + + + + + Bad File Hash + + + + + Network Error + + + + + File System Error + + + + + Invalid + + + + + + Open File + + + + + + + + me + + + + + Save File + + + + diff --git a/src/ricochet-refresh/translation/ricochet_ru.ts b/src/ricochet-refresh/translation/ricochet_ru.ts new file mode 100644 index 00000000..652ddb62 --- /dev/null +++ b/src/ricochet-refresh/translation/ricochet_ru.ts @@ -0,0 +1,1238 @@ + + + + + AboutPreferences + + Ricochet %1 + %1 version, e.g. 1.0.0 + Ricochet %1 + + + + Ricochet-Refresh %1 + %1 version, e.g. 1.0.0 + + + + + Ricochet Refresh version %1 + provides a readable interpretation of the Ricochet-Refresh version number for accessibility tech like screen readers + + + + + The license of Ricochet Refresh and its dependencies + + + + + License + + + + + About + О программе + + + + About page, contains license and version information + summary of the window's contents for accessibility tech like screen readers + + + + + Current Ricochet-Refresh version + + + + + AddContactDialog + + + Share your Ricochet ID to allow connection requests + tells the user the purpose of their Ricochet ID, which is basically a username + Сообщите свой ID, чтобы собеседник узнал Вас + + + + Cancel + label for button which dismisses a dialog + Отмена + + + + Closes the contact add window + description for 'Cancel' button accessibility tech like screen readers + + + + + Add + button label to finish adding a contact/friend + Добавить + + + + Adds the contact to your contact list + description for 'Add' button for accessibility tech like screen readres + + + + + ChatPage + + + Message area + label for accessibility tech like screen readers + + + + + Write the message to be sent here. Press enter to send + description of the text area used to send messages for accessibility tech like screen readers + + + + + ContactActions + + + Warning + + + + + Could not successfully export conversation + + + + + Open Window + Context menu command to open the chat screen in a separate window + Открыть окно + + + + Details... + Context menu command to open a window showing the selected contact's details + Подробнее... + + + + Rename + Context menu command to rename the selected contact + Переименовать + + + + Send File... + Context menu command to initiate a file transfer, opens a system file dialog + + + + + Export Conversation + Context menu command to initiate a chat log export, opens a system file dialog to export to + + + + + Remove + Context menu command to remove a contact from the contact list + Удалить + + + + Contact options + Description of the items in the context menu for accessibility tech like screen readers + + + + + ContactIDField + + + <b>%1</b> is already your contact + Error message showed when user attempts to add a contact already in their contact list + <b>%1</b> уже в Вашем списке контактов + + + + This ID is invalid + Error message showed when the id doesn't comply with spec https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt + + + + + You can't add yourself as a contact + Error message showed when user attempts to add themselves as a contact in their contact list + Вы не можете добавить себя в свой же список контактов + + + + Enter an ID starting with <b>ricochet:</b> + Error message showed when the provided ricochet id is invalid + Введите ID, начинающийся с <b>ricochet:</b> + + + + Copied to clipboard + Message displayed when text is copied to the user's clipboard + Скопировано в буфер + + + + Copy + Text displayed on a button used to copy somethign to the user's clipboard + Копировать + + + + Copies the ricochet id to the clipboard + Text description of ricochet id copy button for accessibility tech like screen readers + + + + + ContactList + + + Online + Section header in the contact list for users which are online + В сети + + + + Offline + Section header in the contact list for users which are offline + Не в сети + + + + Requests + Section header in the contact list for users requesting to be added to the user's contact list + Запросы + + + + Rejected + Section header in the contact list for users that have rejected the user's request to be added to their contact list + Отказано + + + + Status for the given contacts + Description of the section header for accessibility tech like screen readers + + + + + ContactPreferences + + Date added: + Добавлен: + + + Last seen: + Последний раз замечен: + + + Request: + Запрос: + + + Pending connection + Ожидаем соединение + + + Delivered + Доставлено + + + Accepted + Принято + + + Error + Ошибка + + + Rejected + Отказано + + + %1 (Connected) + %1 status, e.g. "Accepted" + %1 (Соединено) + + + Response: + Ответ: + + + + Contact list + Description of the list of contacts for accessibility tech like screen readers + + + + + Contact ID for + Description of text box containing a contact's contact id for accessibility tech like screen readers + + + + + + Unknown user + A placeholder name for a contact whose name we do not know + + + + + Rename + Label for button which allows renaming of a contact + Переименовать + + + + Renames this contact + Description of button which renames a contact for accessibility tech like screen readers + + + + + Remove + Label for button which removes a contact from the contact list + Удалить + + + + Removes this contact + Description of button which removes a user from the contact list for accessibility tech like screen readers + + + + + Preferences for contact + Description of the contents of the 'Contacts' window for accessibility tech like screen readers + + + + + Contact Preferences Window + Name of the contact preferences window for accessibility tech like screen readers + + + + + A list of all your contacts, with their ricochet IDs, and options such as renaming and removing + Description of what user can do in the contact preferences window for accessibility tech like screen readers + + + + + ContactRequestDialog + + + Someone new is asking to connect to you + Descriptive text that is displayed in a popup window when a user tries to add you as a contact + Новый человек хочет пообщаться с Вами + + + + Reject + Label for button which rejects a contact request when pressed + Отказать + + + + Rejects the incoming contact request + Description of what 'Reject' button does for accessibility tech like screen readers + + + + + Accept + Label for button which accepts a contact request when pressed + Принять + + + + Accepts the incoming contact request + Description of what 'Accept' button does for accessibility tech like screen readers + + + + + ContactRequestFields + + + ID: + Label for the contact id text box in the 'add new contact' window + ID: + + + + Name: + Label for the contact nickname text box in the 'add new contact' window + Имя: + + + + Field for the contact's nickname + Description of textbox for setting a contact's nickname for accessibility tech like screen readers + + + + + Message: + Label for the contact greeting message text box in the 'add new contact' window + Сообщение: + + + + Field for the contact's greeting message + Description of textbox for setting a new contact's initial greeting message for accessibility tech like screen readers + + + + + GeneralPreferences + + + Use a single window for conversations + Text description of an option to use one single program window for the contact list and the chats + Использовать одно окно для разговоров + + + Open links in default browser without prompting + Text description of an option to open URLs in default web browser when clicked + Открывать ссылки в браузере по умолчанию без запроса + + + + Play audio notifications + Text description of an option to play audio notifications when contacts log in, log out, and send messages + + + + + + Volume + Label for a slider used to adjust audio notification volume +---------- +Name of the slider used to adjust audio notification volume for accessibility tech like screen readers + + + + + + Language + Label for combobox where users can specify the UI language +---------- +Name of the combobox used to select UI langauge for accessibility tech like screen readers + + + + + Restart Ricochet to apply changes + + + + + What language ricochet will use + Description of what the language combox is for for accessibility tech like screen readers + + + + + LanguagesModel + + + System default + + + + + Main + + + Ricochet Error + Ошибка Ricochet + + + + MainToolBar + + + + Add Contact + Tooltip text for the button that launches the dialog box for adding a new contact +---------- +Name of the button for adding a new contact for accessibility tech like screen readers + Добавить контакт + + + + + Preferences + Tooltip text for the button that launches program preferences window +---------- +Name of the button for launching the preferences window for accessibility tech like screen readres + Настройки + + + + Click to add contacts + Tooltip that displays on first launch indicating how to add a new contact + Нажмите, чтобы добавить контакты + + + + Shows the add contact dialogue + Description of the 'Add Contact' button for accessibility tech like screen readers + + + + + Shows the preferences dialogue + Description of the 'Preferences' button for accessibility tech like screen readers + + + + + MainWindow + + + Version Seperator + + + + + Remove %1 + Удалить %1 + + + + Do you want to permanently remove %1? + Вы действительно хотите удалить %1 навсегда? + + + + Main Toolbar + Name of the main toolbar for accessibility tech like screen readers + + + + + Toolbar with connection status, add contact button, and preferences button + Description of the main toolbar for accessibility tech like screen readers + + + + + Contact pane + Name of the pane holding the user's contacts for accessibility tech like screen readers + + + + + MessageDelegate + + + %1 is offline + %1 nickname + %1 не в сети + + + + File transfer file name + Description of the text displaying the filename of a file transfer, used by accessibility tech like screen readres + + + + + File transfer progress + Description of progress bar displaying the file transfer progress, used by accessibility tech like screen readers + + + + + File transfer status + Description of label displaying the current status of a file transfer, used by accessibility tech like screen readers + + + + + Download + Label for file transfer 'Download' button for accessibility tech like screen readers + + + + + Download file + Description of what the file transfer 'Download' button does for accessibility tech like screen readers + + + + + Cancel or reject + Label for file transfer 'Cancel' button for accessibility tech like screen readers + + + + + Cancels or rejects a file transfer + Description of what the file transfer 'Cancel' button does for accessibility tech like screen readers + + + + Copy ID + Text for context menu command to copy a ricochet contact id to clipboard + Копировать ID + + + Copy Link + Text for context menu command to copy a url to the clipboard + Копировать ссылку + + + Open with Browser + Text for context menu command to open a url in a web browser + Открыть в браузере + + + Add as Contact + Добавить в список контактов + + + + Copy Message + Text for context menu command to copy an entire message to clipboard + + + + + Copy Selection + Text for context menu command to copy selected text to clipboard + + + + + MessageDialogWrapper + + + Remove %1 + A command to remove a contact from the contact list, %1 is the contact's nickname + Удалить %1 + + + + Do you want to permanently remove %1? + Confirmation of contact removal, %1 is the contact's nickname + Вы действительно хотите удалить %1 навсегда? + + + + This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. + Message describing what will happen when a contact is removed from you contact list + Этот контакт больше не сможет писать Вам и будет уведомлен об удалении. Тем не менее, он сможет послать Вам новый запрос. + + + + NetworkSetupWizard + + + This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. + A label with directions for when to use the 'Connect' button + Соединение на этом компьютере свободно от цензуры и фильтров. Я хочу подключиться к сети Tor напрямую. + + + + Connect + Label for button to connect to the Tor network + Соединиться + + + + This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. + A label with directions for when to use the 'Configure' button + Соединение на этом компьютере подвергается цензуре, фильтрации либо проксируется. Мне нужно настроить дополнительные параметры соединения. + + + + Configure + Label for button to configure the Tor daemon beore connecting to the Tor network + Настроить + + + + OfflineStateItem + + + Configure + Button label + Настроить + + + + Details + Button label + Подробности + + + + Connection failed + Label displayed when connecting to the Tor network fails + Не удалось соединиться + + + + Connecting… + Label displayed when in process of connecting, … is ellipsis + Соединение... + + + + OpenBrowserDialog + + <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. + Label displayed when user clicks on a link + <b>Внимание!</b> Открывая ссылки браузером по умолчанию Вы можете нарушить Вашу безопасность и анонимность.<br><br>Вместо этого Вы можете <a href='.'>скопировать ссылку</a>. + + + Don't ask again for links from %1 + Не спрашивать при открытии последующих ссылок от %1 + + + Don't ask again for any links (not recommended!) + Checkbox option text for when user clicks on a link + Не спрашивать при открытии всех последующих ссылок (не рекомендуется!) + + + Open Browser + Label on button to open link in a web browser + Открыть браузер + + + Cancel + Label on cancel button + Отмена + + + + PreferencesDialog + + + Ricochet Preferences + Настройки Ricochet + + + + Menu Tabs + Name of the tab bar for accessibility tech like screen readers + + + + + General + Title of the general settings tab + Основные + + + + Contacts + Title of the contacts list tab + Контакты + + + + Tor + Title of the tor tab, contains tor settings and logs + Tor + + + + About + Title of the about tab, contains license information and ricochet version + О программе + + + + QCocoaMenuItem + + + Preference + Настройки + + + + StartupStatusPage + + + The Tor process was not started successfully. This is most likely an installation or system error. + Error status string displayed when tor daemon does not launch successfully + Процесс Tor не был запущен успешно. Скорее всего, причина в неправильной установке или системной ошибке. + + + + Quit + Button title to quit/terminate the program + Выход + + + + TorBootstrapStatus + + + Connecting to the Tor network… + Text status displayed when tor daemon is connecting, … is ellipsis + Подсоединяемся к сети Tor… + + + + Connection status + Description of the connection status text, used by accessibility tech like screen readers + + + + + Connection progress + Description of the bootstrap progress bar, used by accessibility tech like screen readers + + + + + + Back + Label for button which moves us back to previous screen + Назад + + + + Hide Details + Label for button which hides the tor logs + + + + + Show Details + Label for button which shows the tor logs + + + + + Hides the tor progress log + Description of hide details button, used by accessibility tech like screen readers + + + + + Shows the tor progress log + Description of show details button, used by accessibility tech like screen readers + + + + Hide details + Скрыть подробности + + + Show details + Показать подробности + + + + Done + Label for button which closes the tor connection window + Готово + + + + TorConfigurationPage + + + Does this computer need a proxy to access the internet? + Этот компьютер использует прокси для доступа в Интернет? + + + + Proxy type: + Тип прокси: + + + + + None + Нет + + + + If you need a proxy to access the internet, select one from this list. + Description used by accessibility tech, such as screen readers + + + + + Address: + Label indicating the textbox to place a proxy IP or URL + Адрес: + + + + IP address or hostname + Placeholder text of text box expecting an IP or URL for proxy + IP адрес или имя хоста + + + + Enter the IP address or hostname of the proxy you wish to connect to + Description of what to enter into the IP textbox, used by accessibility tech such as screen readers + + + + + Port: + Label indicating the textbox to place a proxy port + Порт: + + + + Port + Name of the port label, used by accessibility tech such as screen readers + + + + + Enter the port of the proxy you wish to connect to + Description of what to enter into the Port textbox, used by accessibility tech such as screen readers + + + + + Username: + Label indicating the textbox to place the proxy username + Имя пользователя: + + + + + Optional + Textbox placeholder text indicating the field is not required + Необязательно + + + + Username + Name of the username label, used by accessibility tech such as screen readers + + + + + If required, enter the username for the proxy you wish to connect to + Description to enter into the Username textbox, used by accessibility tech such as screen readers + + + + + Password: + Label indicating the textbox to place the proxy password + Пароль: + + + + Password + Name of the password label, used by accessibility tech such as screen readers + + + + + If required, enter the password for the proxy you wish to connect to + Description to enter into the Password textbox, used by accessibility tech such as screen readers + + + + + Does this computer's Internet connection go through a firewall that only allows connections to certain ports? + Description for the purpose of the Allowed Ports textbox + Соединение данного компьютера фильтруется фаерволом, который разрешает доступ только к определенным портам? + + + + Allowed ports: + Label indicating the textbox to place the allowed ports + Открытые порты: + + + + Example: 80,443 + Textbox showing an example entry for the firewall allowed ports entry + Например: 80,443 + + + + Allowed ports + Name of the allowed ports label, used by accessibility tech such as screen readers + + + + + If this computer's Internet connection is censored, you will need to obtain and use bridge relays. + Если соединение этого компьютера подвергается цензуре, Вам необходимо получить и использовать ретрансляторы Tor. + + + + + Enter one or more bridge relays (one per line): + Введите один ретранслятор или более (по одному на строку): + + + + Back + Button label for going back to previous screen + Назад + + + + Connect + Button label for connecting to tor + Соединить + + + + TorLogDisplay + + + Tor log + Name of the text field containg the tor logs, used by accessibility tech such as screen readers + + + + + TorPreferences + + + Running: + Display label that beside it indicates whether tor is running + Запущен: + + + + Running + + + + + Whether tor is running + Description of the value of this label, used by acccessibility tech like screen readers + + + + + + Yes + Да + + + + + No + Нет + + + External + Внешний + + + + Control connected: + Display label that beside it indicates whether ricochet is connected to tor's control port + Контроль присоединенных: + + + + Control connected + + + + + Whether tor control connected + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Circuits established: + Цепи установлены: + + + + Circuits established + + + + + Whether circuits established + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Hidden service: + Скрытый сервис: + + + + Hidden service + + + + + Online + В сети + + + + Offline + Не в сети + + + + Whether a hidden service is up or not + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Version: + Версия: + + + + Version + + + + + Error: <b>%1</b> + %1 is error message + Ошибка: <b>%1</b> + + + + Configure + Настроить + + + + TorStateWidget + + + + Connection failed + Не удалось соединиться + + + + + Connecting… + … is ellipsis + Соединение… + + + + Connecting… (%1%) + %1 is progress percentage, e.g. 100 + Соединение… (%1%) + + + + Online + В сети + + + + Connected + Соединено + + + + Tor status + + + + + shims::ConversationModel + + + Pending + + + + + Accepted + Принято + + + + Rejected + Отказано + + + + Cancelled + + + + + Complete + + + + + Unkown Failure + + + + + Bad File Hash + + + + + Network Error + + + + + File System Error + + + + + Invalid + + + + + + Open File + + + + + + + + me + + + + + Save File + + + + diff --git a/src/ricochet-refresh/translation/ricochet_sl.ts b/src/ricochet-refresh/translation/ricochet_sl.ts new file mode 100644 index 00000000..d4b69f84 --- /dev/null +++ b/src/ricochet-refresh/translation/ricochet_sl.ts @@ -0,0 +1,1257 @@ + + + + + AboutPreferences + + Ricochet %1 + %1 version, e.g. 1.0.0 + Ricochet %1 + + + + Ricochet-Refresh %1 + %1 version, e.g. 1.0.0 + + + + + Ricochet Refresh version %1 + provides a readable interpretation of the Ricochet-Refresh version number for accessibility tech like screen readers + + + + + The license of Ricochet Refresh and its dependencies + + + + + License + + + + + About + Več o + + + + About page, contains license and version information + summary of the window's contents for accessibility tech like screen readers + + + + + Current Ricochet-Refresh version + + + + + AddContactDialog + + + Share your Ricochet ID to allow connection requests + tells the user the purpose of their Ricochet ID, which is basically a username + Za povezavo z drugimi posredujte svoj Ricochet ID. + + + + Cancel + label for button which dismisses a dialog + Prekliči + + + + Closes the contact add window + description for 'Cancel' button accessibility tech like screen readers + + + + + Add + button label to finish adding a contact/friend + Dodaj + + + + Adds the contact to your contact list + description for 'Add' button for accessibility tech like screen readres + + + + + ChatPage + + + Message area + label for accessibility tech like screen readers + + + + + Write the message to be sent here. Press enter to send + description of the text area used to send messages for accessibility tech like screen readers + + + + + ContactActions + + + Warning + + + + + Could not successfully export conversation + + + + + Open Window + Context menu command to open the chat screen in a separate window + Odpri Okno + + + + Details... + Context menu command to open a window showing the selected contact's details + Podrobnosti... + + + + Rename + Context menu command to rename the selected contact + Preimenuj + + + + Send File... + Context menu command to initiate a file transfer, opens a system file dialog + + + + + Export Conversation + Context menu command to initiate a chat log export, opens a system file dialog to export to + + + + + Remove + Context menu command to remove a contact from the contact list + Odstrani + + + + Contact options + Description of the items in the context menu for accessibility tech like screen readers + + + + + ContactIDField + + + <b>%1</b> is already your contact + Error message showed when user attempts to add a contact already in their contact list + <b>%1</b> je že med stiki + + + + This ID is invalid + Error message showed when the id doesn't comply with spec https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt + + + + + You can't add yourself as a contact + Error message showed when user attempts to add themselves as a contact in their contact list + Sebe ne moreš dodati med stike + + + + Enter an ID starting with <b>ricochet:</b> + Error message showed when the provided ricochet id is invalid + Vnesi ID, tako da začneš vnos z <b>ricochet:</b> + + + + Copied to clipboard + Message displayed when text is copied to the user's clipboard + Kopirano v odložišče + + + + Copy + Text displayed on a button used to copy somethign to the user's clipboard + Kopiraj + + + + Copies the ricochet id to the clipboard + Text description of ricochet id copy button for accessibility tech like screen readers + + + + + ContactList + + + Online + Section header in the contact list for users which are online + Povezan + + + + Offline + Section header in the contact list for users which are offline + Brez povezave + + + + Requests + Section header in the contact list for users requesting to be added to the user's contact list + Zahteve + + + + Rejected + Section header in the contact list for users that have rejected the user's request to be added to their contact list + Zavrnjeno + + + + Status for the given contacts + Description of the section header for accessibility tech like screen readers + + + + Outdated + Zastarelo + + + + ContactPreferences + + Date added: + Dodan datum: + + + Last seen: + Nazadnje viden: + + + Request: + Zahteva: + + + Pending connection + Povezovanje je v teku + + + Delivered + Dostavljeno + + + Accepted + Sprejeto + + + Error + Napaka + + + Rejected + Zavrnjeno + + + %1 (Connected) + %1 status, e.g. "Accepted" + %1 (Povezan) + + + Response: + Odgovor: + + + + Contact list + Description of the list of contacts for accessibility tech like screen readers + + + + + Contact ID for + Description of text box containing a contact's contact id for accessibility tech like screen readers + + + + + + Unknown user + A placeholder name for a contact whose name we do not know + + + + + Rename + Label for button which allows renaming of a contact + Preimenuj + + + + Renames this contact + Description of button which renames a contact for accessibility tech like screen readers + + + + + Remove + Label for button which removes a contact from the contact list + Odstrani + + + + Removes this contact + Description of button which removes a user from the contact list for accessibility tech like screen readers + + + + + Preferences for contact + Description of the contents of the 'Contacts' window for accessibility tech like screen readers + + + + + Contact Preferences Window + Name of the contact preferences window for accessibility tech like screen readers + + + + + A list of all your contacts, with their ricochet IDs, and options such as renaming and removing + Description of what user can do in the contact preferences window for accessibility tech like screen readers + + + + + ContactRequestDialog + + + Someone new is asking to connect to you + Descriptive text that is displayed in a popup window when a user tries to add you as a contact + Nekdo nov se želi povezati z vami + + + + Reject + Label for button which rejects a contact request when pressed + Zavrni + + + + Rejects the incoming contact request + Description of what 'Reject' button does for accessibility tech like screen readers + + + + + Accept + Label for button which accepts a contact request when pressed + Sprejmi + + + + Accepts the incoming contact request + Description of what 'Accept' button does for accessibility tech like screen readers + + + + + ContactRequestFields + + + ID: + Label for the contact id text box in the 'add new contact' window + ID: + + + + Name: + Label for the contact nickname text box in the 'add new contact' window + Ime: + + + + Field for the contact's nickname + Description of textbox for setting a contact's nickname for accessibility tech like screen readers + + + + + Message: + Label for the contact greeting message text box in the 'add new contact' window + Sporočilo: + + + + Field for the contact's greeting message + Description of textbox for setting a new contact's initial greeting message for accessibility tech like screen readers + + + + + GeneralPreferences + + + Use a single window for conversations + Text description of an option to use one single program window for the contact list and the chats + Uporabljaj enotno okno za pogovore + + + Open links in default browser without prompting + Text description of an option to open URLs in default web browser when clicked + Brez spraševanja odpri povezave v privzetem brskalniku + + + + Play audio notifications + Text description of an option to play audio notifications when contacts log in, log out, and send messages + Predvajaj zvočna obvestila + + + + + Volume + Label for a slider used to adjust audio notification volume +---------- +Name of the slider used to adjust audio notification volume for accessibility tech like screen readers + Glasnost + + + + + Language + Label for combobox where users can specify the UI language +---------- +Name of the combobox used to select UI langauge for accessibility tech like screen readers + Jezik + + + + Restart Ricochet to apply changes + Za uveljavitev sprememb ponovno zaženite Ricochet + + + + What language ricochet will use + Description of what the language combox is for for accessibility tech like screen readers + + + + + LanguagePreferences + + Select Language + Izberi jezik + + + Restart Ricochet to apply changes + Za uveljavitev sprememb ponovno zaženite Ricochet + + + + LanguagesModel + + + System default + Sistemsko privzeto + + + + Main + + + Ricochet Error + Napaka Ricochet + + + + MainToolBar + + + + Add Contact + Tooltip text for the button that launches the dialog box for adding a new contact +---------- +Name of the button for adding a new contact for accessibility tech like screen readers + Dodaj stik + + + + + Preferences + Tooltip text for the button that launches program preferences window +---------- +Name of the button for launching the preferences window for accessibility tech like screen readres + Nastavitve + + + + Click to add contacts + Tooltip that displays on first launch indicating how to add a new contact + Klikni za dodajanje kontaktov + + + + Shows the add contact dialogue + Description of the 'Add Contact' button for accessibility tech like screen readers + + + + + Shows the preferences dialogue + Description of the 'Preferences' button for accessibility tech like screen readers + + + + + MainWindow + + + Version Seperator + + + + + Remove %1 + Odstrani %1 + + + + Do you want to permanently remove %1? + Ali želte za vselej odstraniti %1? + + + + Main Toolbar + Name of the main toolbar for accessibility tech like screen readers + + + + + Toolbar with connection status, add contact button, and preferences button + Description of the main toolbar for accessibility tech like screen readers + + + + + Contact pane + Name of the pane holding the user's contacts for accessibility tech like screen readers + + + + + MessageDelegate + + + %1 is offline + %1 nickname + %1 ni povezan + + + + File transfer file name + Description of the text displaying the filename of a file transfer, used by accessibility tech like screen readres + + + + + File transfer progress + Description of progress bar displaying the file transfer progress, used by accessibility tech like screen readers + + + + + File transfer status + Description of label displaying the current status of a file transfer, used by accessibility tech like screen readers + + + + + Download + Label for file transfer 'Download' button for accessibility tech like screen readers + + + + + Download file + Description of what the file transfer 'Download' button does for accessibility tech like screen readers + + + + + Cancel or reject + Label for file transfer 'Cancel' button for accessibility tech like screen readers + + + + + Cancels or rejects a file transfer + Description of what the file transfer 'Cancel' button does for accessibility tech like screen readers + + + + Copy ID + Text for context menu command to copy a ricochet contact id to clipboard + Kopiraj ID + + + Copy Link + Text for context menu command to copy a url to the clipboard + Kopiraj povezavo + + + Open with Browser + Text for context menu command to open a url in a web browser + Odpri z brskalnikom + + + Add as Contact + Dodaj kot stik + + + + Copy Message + Text for context menu command to copy an entire message to clipboard + Kopiraj sporočilo + + + + Copy Selection + Text for context menu command to copy selected text to clipboard + Kopiraj izbor + + + + MessageDialogWrapper + + + Remove %1 + A command to remove a contact from the contact list, %1 is the contact's nickname + Odstrani %1 + + + + Do you want to permanently remove %1? + Confirmation of contact removal, %1 is the contact's nickname + Ali želte za vselej odstraniti %1? + + + + This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. + Message describing what will happen when a contact is removed from you contact list + Ta oseba vam ne bo mogla več pošiljati sporočil in bo obveščena o odstranitvi z vašega seznama. Lahko vam pošlje novo zahtevo za povezavo. + + + + NetworkSetupWizard + + + This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. + A label with directions for when to use the 'Connect' button + Povezava tega računalnika z medmrežjem je brez ovir. Želim se neposredno povezati z omrežjem Tor. + + + + Connect + Label for button to connect to the Tor network + Poveži se + + + + This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. + A label with directions for when to use the 'Configure' button + Povezava tega računalnika z medmrežjem je cenzurirana, filtrirana ali preusmerjena. Potrebne so ročne nastavitve. + + + + Configure + Label for button to configure the Tor daemon beore connecting to the Tor network + Nastavi + + + + OfflineStateItem + + + Configure + Button label + Nastavi + + + + Details + Button label + Podrobnosti + + + + Connection failed + Label displayed when connecting to the Tor network fails + Povezovanje ni uspelo + + + + Connecting… + Label displayed when in process of connecting, … is ellipsis + Povezujem... + + + + OpenBrowserDialog + + <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. + Label displayed when user clicks on a link + <b>Pozor!</b> Odpiranje povezav s privzetim brskalnikom škoduje vaši zasebnosti in varnosti.<br><br>Lahko pa jih namesto tega<a href='.'>prekopirate v odložišče</a>. + + + Don't ask again for links from %1 + Ne sprašuj več za povezavo z %1 + + + Don't ask again for any links (not recommended!) + Checkbox option text for when user clicks on a link + Ne sprašuj več za nobeno povezavo (ni priporočljivo!) + + + Open Browser + Label on button to open link in a web browser + Odpri brskalnik + + + Cancel + Label on cancel button + Prekliči + + + + PreferencesDialog + + + Ricochet Preferences + Nastavitve Ricochet + + + + Menu Tabs + Name of the tab bar for accessibility tech like screen readers + + + + + General + Title of the general settings tab + Splošno + + + Language + Jezik + + + + Contacts + Title of the contacts list tab + Stiki + + + + Tor + Title of the tor tab, contains tor settings and logs + Tor + + + + About + Title of the about tab, contains license information and ricochet version + Več o + + + + QCocoaMenuItem + + + Preference + Izbira + + + + StartupStatusPage + + + The Tor process was not started successfully. This is most likely an installation or system error. + Error status string displayed when tor daemon does not launch successfully + Proces Tor se ni uspel uspešno zagnati. Najverjetneje gre za problem pri instalaciji ali sistemsko napako. + + + + Quit + Button title to quit/terminate the program + Končaj + + + + TorBootstrapStatus + + + Connecting to the Tor network… + Text status displayed when tor daemon is connecting, … is ellipsis + Povezujem se z omrežjem Tor... + + + + Connection status + Description of the connection status text, used by accessibility tech like screen readers + + + + + Connection progress + Description of the bootstrap progress bar, used by accessibility tech like screen readers + + + + + + Back + Label for button which moves us back to previous screen + Nazaj + + + + Hide Details + Label for button which hides the tor logs + + + + + Show Details + Label for button which shows the tor logs + + + + + Hides the tor progress log + Description of hide details button, used by accessibility tech like screen readers + + + + + Shows the tor progress log + Description of show details button, used by accessibility tech like screen readers + + + + Hide details + Skrij podrobnosti + + + Show details + Prikaži podrobnosti + + + + Done + Label for button which closes the tor connection window + Izvršeno + + + + TorConfigurationPage + + + Does this computer need a proxy to access the internet? + Potrebuje ta računalnik proksi za dostop do medmrežja? + + + + Proxy type: + Vrsta proksija: + + + + + None + Brez + + + + If you need a proxy to access the internet, select one from this list. + Description used by accessibility tech, such as screen readers + + + + + Address: + Label indicating the textbox to place a proxy IP or URL + Naslov + + + + IP address or hostname + Placeholder text of text box expecting an IP or URL for proxy + Naslov IP ali ime gostitelja + + + + Enter the IP address or hostname of the proxy you wish to connect to + Description of what to enter into the IP textbox, used by accessibility tech such as screen readers + + + + + Port: + Label indicating the textbox to place a proxy port + Vrata: + + + + Port + Name of the port label, used by accessibility tech such as screen readers + + + + + Enter the port of the proxy you wish to connect to + Description of what to enter into the Port textbox, used by accessibility tech such as screen readers + + + + + Username: + Label indicating the textbox to place the proxy username + Uporabniško ime: + + + + + Optional + Textbox placeholder text indicating the field is not required + Izbirno + + + + Username + Name of the username label, used by accessibility tech such as screen readers + + + + + If required, enter the username for the proxy you wish to connect to + Description to enter into the Username textbox, used by accessibility tech such as screen readers + + + + + Password: + Label indicating the textbox to place the proxy password + Geslo: + + + + Password + Name of the password label, used by accessibility tech such as screen readers + + + + + If required, enter the password for the proxy you wish to connect to + Description to enter into the Password textbox, used by accessibility tech such as screen readers + + + + + Does this computer's Internet connection go through a firewall that only allows connections to certain ports? + Description for the purpose of the Allowed Ports textbox + Ali poteka povezava tega računalnika z medmrežjem čez požarni zid, ki dovoljuje povezavo le skozi določena vrata? + + + + Allowed ports: + Label indicating the textbox to place the allowed ports + Dovoljena vrata: + + + + Example: 80,443 + Textbox showing an example entry for the firewall allowed ports entry + Primer: 80,443 + + + + Allowed ports + Name of the allowed ports label, used by accessibility tech such as screen readers + + + + + If this computer's Internet connection is censored, you will need to obtain and use bridge relays. + Če je povezava tega računalnika z medmrežjem cenzurirana, morate pridobiti posredniške mostove - bridge relays. + + + + + Enter one or more bridge relays (one per line): + Vnesite enega ali več posredniških mostov (enega na vrstico): + + + + Back + Button label for going back to previous screen + Nazaj + + + + Connect + Button label for connecting to tor + Poveži se + + + + TorLogDisplay + + + Tor log + Name of the text field containg the tor logs, used by accessibility tech such as screen readers + + + + + TorPreferences + + + Running: + Display label that beside it indicates whether tor is running + Teče: + + + + Running + + + + + Whether tor is running + Description of the value of this label, used by acccessibility tech like screen readers + + + + + + Yes + Da + + + + + No + Ne + + + External + Zunanje + + + + Control connected: + Display label that beside it indicates whether ricochet is connected to tor's control port + Nadzorno povezan: + + + + Control connected + + + + + Whether tor control connected + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Circuits established: + Vzpostavljen krog: + + + + Circuits established + + + + + Whether circuits established + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Hidden service: + Skrite storitve: + + + + Hidden service + + + + + Online + Povezan + + + + Offline + Brez povezave + + + + Whether a hidden service is up or not + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Version: + Različica: + + + + Version + + + + + Error: <b>%1</b> + %1 is error message + Napaka: <b>%1</b> + + + + Configure + Nastavi + + + + TorStateWidget + + + + Connection failed + Povezava ni uspela + + + + + Connecting… + … is ellipsis + Povezujem... + + + + Connecting… (%1%) + %1 is progress percentage, e.g. 100 + Povezujem… (%1%) + + + + Online + Povezan + + + + Connected + Povezan + + + + Tor status + + + + + shims::ConversationModel + + + Pending + + + + + Accepted + Sprejeto + + + + Rejected + Zavrnjeno + + + + Cancelled + + + + + Complete + + + + + Unkown Failure + + + + + Bad File Hash + + + + + Network Error + + + + + File System Error + + + + + Invalid + + + + + + Open File + + + + + + + + me + + + + + Save File + + + + diff --git a/src/ricochet-refresh/translation/ricochet_sq.ts b/src/ricochet-refresh/translation/ricochet_sq.ts new file mode 100644 index 00000000..f1b4dee6 --- /dev/null +++ b/src/ricochet-refresh/translation/ricochet_sq.ts @@ -0,0 +1,1257 @@ + + + + + AboutPreferences + + Ricochet %1 + %1 version, e.g. 1.0.0 + Ricochet %1 + + + + Ricochet-Refresh %1 + %1 version, e.g. 1.0.0 + + + + + Ricochet Refresh version %1 + provides a readable interpretation of the Ricochet-Refresh version number for accessibility tech like screen readers + + + + + The license of Ricochet Refresh and its dependencies + + + + + License + + + + + About + Mbi + + + + About page, contains license and version information + summary of the window's contents for accessibility tech like screen readers + + + + + Current Ricochet-Refresh version + + + + + AddContactDialog + + + Share your Ricochet ID to allow connection requests + tells the user the purpose of their Ricochet ID, which is basically a username + Ndani me të tjerët ID-në tuaj Ricochet që të lejoni kërkesa lidhjesh + + + + Cancel + label for button which dismisses a dialog + Anulojeni + + + + Closes the contact add window + description for 'Cancel' button accessibility tech like screen readers + + + + + Add + button label to finish adding a contact/friend + Shtoje + + + + Adds the contact to your contact list + description for 'Add' button for accessibility tech like screen readres + + + + + ChatPage + + + Message area + label for accessibility tech like screen readers + + + + + Write the message to be sent here. Press enter to send + description of the text area used to send messages for accessibility tech like screen readers + + + + + ContactActions + + + Warning + + + + + Could not successfully export conversation + + + + + Open Window + Context menu command to open the chat screen in a separate window + Hap Dritare + + + + Details... + Context menu command to open a window showing the selected contact's details + Hollësi… + + + + Rename + Context menu command to rename the selected contact + Riemërtojeni + + + + Send File... + Context menu command to initiate a file transfer, opens a system file dialog + + + + + Export Conversation + Context menu command to initiate a chat log export, opens a system file dialog to export to + + + + + Remove + Context menu command to remove a contact from the contact list + Hiqe + + + + Contact options + Description of the items in the context menu for accessibility tech like screen readers + + + + + ContactIDField + + + <b>%1</b> is already your contact + Error message showed when user attempts to add a contact already in their contact list + <b>%1</b> është tashmë kontakt i juaji + + + + This ID is invalid + Error message showed when the id doesn't comply with spec https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt + + + + + You can't add yourself as a contact + Error message showed when user attempts to add themselves as a contact in their contact list + S’mund të shtoni veten si kontakt + + + + Enter an ID starting with <b>ricochet:</b> + Error message showed when the provided ricochet id is invalid + Jepni një ID që fillon me <b>ricochet:</b> + + + + Copied to clipboard + Message displayed when text is copied to the user's clipboard + U kopjua në të papastër + + + + Copy + Text displayed on a button used to copy somethign to the user's clipboard + Kopjoje + + + + Copies the ricochet id to the clipboard + Text description of ricochet id copy button for accessibility tech like screen readers + + + + + ContactList + + + Online + Section header in the contact list for users which are online + Në linjë + + + + Offline + Section header in the contact list for users which are offline + Jo në linjë + + + + Requests + Section header in the contact list for users requesting to be added to the user's contact list + Kërkesa + + + + Rejected + Section header in the contact list for users that have rejected the user's request to be added to their contact list + E hedhur tej + + + + Status for the given contacts + Description of the section header for accessibility tech like screen readers + + + + Outdated + E vjetruar + + + + ContactPreferences + + Date added: + Datë shtimi: + + + Last seen: + Parë së fundi më: + + + Request: + Kërkesë: + + + Pending connection + Lidhje në pritje të pranimit + + + Delivered + Të dërguar + + + Accepted + Të pranuara + + + Error + Gabim + + + Rejected + E hedhur tej + + + %1 (Connected) + %1 status, e.g. "Accepted" + %1 (I lidhur) + + + Response: + Përgjigje: + + + + Contact list + Description of the list of contacts for accessibility tech like screen readers + + + + + Contact ID for + Description of text box containing a contact's contact id for accessibility tech like screen readers + + + + + + Unknown user + A placeholder name for a contact whose name we do not know + + + + + Rename + Label for button which allows renaming of a contact + Riemërtojeni + + + + Renames this contact + Description of button which renames a contact for accessibility tech like screen readers + + + + + Remove + Label for button which removes a contact from the contact list + Hiqe + + + + Removes this contact + Description of button which removes a user from the contact list for accessibility tech like screen readers + + + + + Preferences for contact + Description of the contents of the 'Contacts' window for accessibility tech like screen readers + + + + + Contact Preferences Window + Name of the contact preferences window for accessibility tech like screen readers + + + + + A list of all your contacts, with their ricochet IDs, and options such as renaming and removing + Description of what user can do in the contact preferences window for accessibility tech like screen readers + + + + + ContactRequestDialog + + + Someone new is asking to connect to you + Descriptive text that is displayed in a popup window when a user tries to add you as a contact + Dikush i ri po kërkon të lidhet me ju + + + + Reject + Label for button which rejects a contact request when pressed + Hidheni tej + + + + Rejects the incoming contact request + Description of what 'Reject' button does for accessibility tech like screen readers + + + + + Accept + Label for button which accepts a contact request when pressed + Pranojeni + + + + Accepts the incoming contact request + Description of what 'Accept' button does for accessibility tech like screen readers + + + + + ContactRequestFields + + + ID: + Label for the contact id text box in the 'add new contact' window + ID: + + + + Name: + Label for the contact nickname text box in the 'add new contact' window + Emër: + + + + Field for the contact's nickname + Description of textbox for setting a contact's nickname for accessibility tech like screen readers + + + + + Message: + Label for the contact greeting message text box in the 'add new contact' window + Mesazh: + + + + Field for the contact's greeting message + Description of textbox for setting a new contact's initial greeting message for accessibility tech like screen readers + + + + + GeneralPreferences + + + Use a single window for conversations + Text description of an option to use one single program window for the contact list and the chats + Përdor një dritare të vetme për bisedat + + + Open links in default browser without prompting + Text description of an option to open URLs in default web browser when clicked + Hapi lidhjet në shfletuesin parazgjedhje, pa pyetur + + + + Play audio notifications + Text description of an option to play audio notifications when contacts log in, log out, and send messages + Luaji njoftimet audion + + + + + Volume + Label for a slider used to adjust audio notification volume +---------- +Name of the slider used to adjust audio notification volume for accessibility tech like screen readers + Volum + + + + + Language + Label for combobox where users can specify the UI language +---------- +Name of the combobox used to select UI langauge for accessibility tech like screen readers + Gjuhë + + + + Restart Ricochet to apply changes + Riniseni Ricochet-in që të zbatohen ndryshimet + + + + What language ricochet will use + Description of what the language combox is for for accessibility tech like screen readers + + + + + LanguagePreferences + + Select Language + Përzgjidhni Gjuhë + + + Restart Ricochet to apply changes + Riniseni Ricochet-in që të zbatohen ndryshimet + + + + LanguagesModel + + + System default + Parazgjedhje sistemi + + + + Main + + + Ricochet Error + Gabim Ricochet-i + + + + MainToolBar + + + + Add Contact + Tooltip text for the button that launches the dialog box for adding a new contact +---------- +Name of the button for adding a new contact for accessibility tech like screen readers + Shtoni Kontakt + + + + + Preferences + Tooltip text for the button that launches program preferences window +---------- +Name of the button for launching the preferences window for accessibility tech like screen readres + Parapëlqime + + + + Click to add contacts + Tooltip that displays on first launch indicating how to add a new contact + Klikoni që të shtoni kontakte + + + + Shows the add contact dialogue + Description of the 'Add Contact' button for accessibility tech like screen readers + + + + + Shows the preferences dialogue + Description of the 'Preferences' button for accessibility tech like screen readers + + + + + MainWindow + + + Version Seperator + + + + + Remove %1 + Hiqe + + + + Do you want to permanently remove %1? + Doni të hiqet %1 përgjithmonë? + + + + Main Toolbar + Name of the main toolbar for accessibility tech like screen readers + + + + + Toolbar with connection status, add contact button, and preferences button + Description of the main toolbar for accessibility tech like screen readers + + + + + Contact pane + Name of the pane holding the user's contacts for accessibility tech like screen readers + + + + + MessageDelegate + + + %1 is offline + %1 nickname + %1 s’është në linjë + + + + File transfer file name + Description of the text displaying the filename of a file transfer, used by accessibility tech like screen readres + + + + + File transfer progress + Description of progress bar displaying the file transfer progress, used by accessibility tech like screen readers + + + + + File transfer status + Description of label displaying the current status of a file transfer, used by accessibility tech like screen readers + + + + + Download + Label for file transfer 'Download' button for accessibility tech like screen readers + + + + + Download file + Description of what the file transfer 'Download' button does for accessibility tech like screen readers + + + + + Cancel or reject + Label for file transfer 'Cancel' button for accessibility tech like screen readers + + + + + Cancels or rejects a file transfer + Description of what the file transfer 'Cancel' button does for accessibility tech like screen readers + + + + Copy ID + Text for context menu command to copy a ricochet contact id to clipboard + Kopjo ID-në + + + Copy Link + Text for context menu command to copy a url to the clipboard + Kopjo Lidhjen + + + Open with Browser + Text for context menu command to open a url in a web browser + Hape me Shfletues + + + Add as Contact + Shtojeni si Kontakt + + + + Copy Message + Text for context menu command to copy an entire message to clipboard + Kopjo Mesazhin + + + + Copy Selection + Text for context menu command to copy selected text to clipboard + Kopjo Përzgjedhjen + + + + MessageDialogWrapper + + + Remove %1 + A command to remove a contact from the contact list, %1 is the contact's nickname + Hiqe %1 + + + + Do you want to permanently remove %1? + Confirmation of contact removal, %1 is the contact's nickname + Doni të hiqet %1 përgjithmonë? + + + + This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. + Message describing what will happen when a contact is removed from you contact list + Ky kontakt s’do të jetë më në gjendje t’ju dërgojë mesazhe, dhe do të njoftohet për heqjen. Ai mund të zgjedhë t’ju dërgojë një kërkesë të re lidhjeje. + + + + NetworkSetupWizard + + + This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. + A label with directions for when to use the 'Connect' button + Lidhja në Internet e këtij kompjuteri është pa pengesa. Do të doja të lidhem drejtpërsëdrejti me rrjetin Tor. + + + + Connect + Label for button to connect to the Tor network + Lidhu + + + + This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. + A label with directions for when to use the 'Configure' button + Lidhja në Internet e këtij kompjuteri është e censuruar, e filtruar ose bëhet përmes një ndërmjetësi. Kam nevojë të formësoj rregullime rrjeti. + + + + Configure + Label for button to configure the Tor daemon beore connecting to the Tor network + Formësojeni + + + + OfflineStateItem + + + Configure + Button label + Formësojeni + + + + Details + Button label + Hollësi + + + + Connection failed + Label displayed when connecting to the Tor network fails + Lidhja dështoi + + + + Connecting… + Label displayed when in process of connecting, … is ellipsis + Po lidhet… + + + + OpenBrowserDialog + + <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. + Label displayed when user clicks on a link + <b>Kujdes!</b> Hapja e lidhjeve me shfletuesin tuaj parazgjedhje do të rrezikojë sigurinë dhe anonimitetin tuaj.<br><br>Më mirë mund <a href='.'>ta kopjoni në të papastër</a>. + + + Don't ask again for links from %1 + Mos pyet sërish për lidhje nga %1 + + + Don't ask again for any links (not recommended!) + Checkbox option text for when user clicks on a link + Mos pyet më, për çfarëdo lidhje (e pakëshillueshme!) + + + Open Browser + Label on button to open link in a web browser + Hap Shfletues + + + Cancel + Label on cancel button + Anulojeni + + + + PreferencesDialog + + + Ricochet Preferences + Parapëlqime për Ricochet-in + + + + Menu Tabs + Name of the tab bar for accessibility tech like screen readers + + + + + General + Title of the general settings tab + Të përgjithshme + + + Language + Gjuhë + + + + Contacts + Title of the contacts list tab + Kontakte + + + + Tor + Title of the tor tab, contains tor settings and logs + Tor + + + + About + Title of the about tab, contains license information and ricochet version + Mbi + + + + QCocoaMenuItem + + + Preference + Parapëlqim + + + + StartupStatusPage + + + The Tor process was not started successfully. This is most likely an installation or system error. + Error status string displayed when tor daemon does not launch successfully + Procesi Tor s’u nis me sukses. Ka shumë të ngjarë që ky të jetë një gabim instalimi ose sistemi. + + + + Quit + Button title to quit/terminate the program + Dilni + + + + TorBootstrapStatus + + + Connecting to the Tor network… + Text status displayed when tor daemon is connecting, … is ellipsis + Po lidhet me rrjetin TOR… + + + + Connection status + Description of the connection status text, used by accessibility tech like screen readers + + + + + Connection progress + Description of the bootstrap progress bar, used by accessibility tech like screen readers + + + + + + Back + Label for button which moves us back to previous screen + Mbrapsht + + + + Hide Details + Label for button which hides the tor logs + + + + + Show Details + Label for button which shows the tor logs + + + + + Hides the tor progress log + Description of hide details button, used by accessibility tech like screen readers + + + + + Shows the tor progress log + Description of show details button, used by accessibility tech like screen readers + + + + Hide details + Fshihi hollësitë + + + Show details + Shfaqi hollësitë + + + + Done + Label for button which closes the tor connection window + U bë + + + + TorConfigurationPage + + + Does this computer need a proxy to access the internet? + Ka nevojë për ndërmjetës ky kompjuter, që të lidhet në internet? + + + + Proxy type: + Lloj ndërmjetësi: + + + + + None + Asnjë + + + + If you need a proxy to access the internet, select one from this list. + Description used by accessibility tech, such as screen readers + + + + + Address: + Label indicating the textbox to place a proxy IP or URL + Adresë: + + + + IP address or hostname + Placeholder text of text box expecting an IP or URL for proxy + Adresë IP ose emër strehe + + + + Enter the IP address or hostname of the proxy you wish to connect to + Description of what to enter into the IP textbox, used by accessibility tech such as screen readers + + + + + Port: + Label indicating the textbox to place a proxy port + Portë: + + + + Port + Name of the port label, used by accessibility tech such as screen readers + + + + + Enter the port of the proxy you wish to connect to + Description of what to enter into the Port textbox, used by accessibility tech such as screen readers + + + + + Username: + Label indicating the textbox to place the proxy username + Emër përdoruesi: + + + + + Optional + Textbox placeholder text indicating the field is not required + Opsionale + + + + Username + Name of the username label, used by accessibility tech such as screen readers + + + + + If required, enter the username for the proxy you wish to connect to + Description to enter into the Username textbox, used by accessibility tech such as screen readers + + + + + Password: + Label indicating the textbox to place the proxy password + Fjalëkalim: + + + + Password + Name of the password label, used by accessibility tech such as screen readers + + + + + If required, enter the password for the proxy you wish to connect to + Description to enter into the Password textbox, used by accessibility tech such as screen readers + + + + + Does this computer's Internet connection go through a firewall that only allows connections to certain ports? + Description for the purpose of the Allowed Ports textbox + A kalon përmes një firewall-i lidhja internet e këtij kompjuteri, i cili lejon lidhje vetëm te disa prej portave? + + + + Allowed ports: + Label indicating the textbox to place the allowed ports + Porta të lejuara: + + + + Example: 80,443 + Textbox showing an example entry for the firewall allowed ports entry + Shembull: 80,443 + + + + Allowed ports + Name of the allowed ports label, used by accessibility tech such as screen readers + + + + + If this computer's Internet connection is censored, you will need to obtain and use bridge relays. + Nëse lidhja në Internet e këtij kompjuteri është e censuruar, do t’ju duhet të merrni dhe përdorni <i>bridge relays</i>. + + + + + Enter one or more bridge relays (one per line): + Jepni një ose më tepër <i>bridge relays</i> (një për rresht): + + + + Back + Button label for going back to previous screen + Mbrapsht + + + + Connect + Button label for connecting to tor + Lidhu + + + + TorLogDisplay + + + Tor log + Name of the text field containg the tor logs, used by accessibility tech such as screen readers + + + + + TorPreferences + + + Running: + Display label that beside it indicates whether tor is running + Xhiron: + + + + Running + + + + + Whether tor is running + Description of the value of this label, used by acccessibility tech like screen readers + + + + + + Yes + Po + + + + + No + Jo + + + External + I jashtëm + + + + Control connected: + Display label that beside it indicates whether ricochet is connected to tor's control port + I lidhur me kontrollin: + + + + Control connected + + + + + Whether tor control connected + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Circuits established: + Qarqe të vendosur: + + + + Circuits established + + + + + Whether circuits established + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Hidden service: + Shërbim i fshehur: + + + + Hidden service + + + + + Online + Në linjë + + + + Offline + Jo në linjë + + + + Whether a hidden service is up or not + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Version: + Version: + + + + Version + + + + + Error: <b>%1</b> + %1 is error message + Gabim: <b>%1</b> + + + + Configure + Formësojeni + + + + TorStateWidget + + + + Connection failed + Lidhja dështoi + + + + + Connecting… + … is ellipsis + Po lidhet… + + + + Connecting… (%1%) + %1 is progress percentage, e.g. 100 + Po lidhet… (%1%) + + + + Online + Në linjë + + + + Connected + I lidhur + + + + Tor status + + + + + shims::ConversationModel + + + Pending + + + + + Accepted + Të pranuara + + + + Rejected + E hedhur tej + + + + Cancelled + + + + + Complete + + + + + Unkown Failure + + + + + Bad File Hash + + + + + Network Error + + + + + File System Error + + + + + Invalid + + + + + + Open File + + + + + + + + me + + + + + Save File + + + + diff --git a/src/ricochet-refresh/translation/ricochet_sv.ts b/src/ricochet-refresh/translation/ricochet_sv.ts new file mode 100644 index 00000000..91cb4a29 --- /dev/null +++ b/src/ricochet-refresh/translation/ricochet_sv.ts @@ -0,0 +1,1257 @@ + + + + + AboutPreferences + + Ricochet %1 + %1 version, e.g. 1.0.0 + Ricochet %1 + + + + Ricochet-Refresh %1 + %1 version, e.g. 1.0.0 + + + + + Ricochet Refresh version %1 + provides a readable interpretation of the Ricochet-Refresh version number for accessibility tech like screen readers + + + + + The license of Ricochet Refresh and its dependencies + + + + + License + + + + + About + Om + + + + About page, contains license and version information + summary of the window's contents for accessibility tech like screen readers + + + + + Current Ricochet-Refresh version + + + + + AddContactDialog + + + Share your Ricochet ID to allow connection requests + tells the user the purpose of their Ricochet ID, which is basically a username + Dela ditt Ricochet-ID så att du kan ta emot förfrågningar + + + + Cancel + label for button which dismisses a dialog + Avbryt + + + + Closes the contact add window + description for 'Cancel' button accessibility tech like screen readers + + + + + Add + button label to finish adding a contact/friend + Lägg till + + + + Adds the contact to your contact list + description for 'Add' button for accessibility tech like screen readres + + + + + ChatPage + + + Message area + label for accessibility tech like screen readers + + + + + Write the message to be sent here. Press enter to send + description of the text area used to send messages for accessibility tech like screen readers + + + + + ContactActions + + + Warning + + + + + Could not successfully export conversation + + + + + Open Window + Context menu command to open the chat screen in a separate window + Öppna fönster + + + + Details... + Context menu command to open a window showing the selected contact's details + Detaljer... + + + + Rename + Context menu command to rename the selected contact + Ändra namn + + + + Send File... + Context menu command to initiate a file transfer, opens a system file dialog + + + + + Export Conversation + Context menu command to initiate a chat log export, opens a system file dialog to export to + + + + + Remove + Context menu command to remove a contact from the contact list + Ta bort + + + + Contact options + Description of the items in the context menu for accessibility tech like screen readers + + + + + ContactIDField + + + <b>%1</b> is already your contact + Error message showed when user attempts to add a contact already in their contact list + <b>%1</b> finns redan bland dina kontakter + + + + This ID is invalid + Error message showed when the id doesn't comply with spec https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt + + + + + You can't add yourself as a contact + Error message showed when user attempts to add themselves as a contact in their contact list + Du kan inte lägga till dig själv som kontakt + + + + Enter an ID starting with <b>ricochet:</b> + Error message showed when the provided ricochet id is invalid + Fyll i ett ID som startar med <b>ricochet:</b> + + + + Copied to clipboard + Message displayed when text is copied to the user's clipboard + Kopierat till Urklipp + + + + Copy + Text displayed on a button used to copy somethign to the user's clipboard + Kopiera + + + + Copies the ricochet id to the clipboard + Text description of ricochet id copy button for accessibility tech like screen readers + + + + + ContactList + + + Online + Section header in the contact list for users which are online + Online + + + + Offline + Section header in the contact list for users which are offline + Offline + + + + Requests + Section header in the contact list for users requesting to be added to the user's contact list + Förfrågningar + + + + Rejected + Section header in the contact list for users that have rejected the user's request to be added to their contact list + Nekad + + + + Status for the given contacts + Description of the section header for accessibility tech like screen readers + + + + Outdated + Inaktuell + + + + ContactPreferences + + Date added: + Tillagd datum: + + + Last seen: + Sågs senast: + + + Request: + Förfrågan: + + + Pending connection + Inväntar anslutning + + + Delivered + Levererad + + + Accepted + Godkänd + + + Error + Fel + + + Rejected + Nekad + + + %1 (Connected) + %1 status, e.g. "Accepted" + %1 (Ansluten) + + + Response: + Svar: + + + + Contact list + Description of the list of contacts for accessibility tech like screen readers + + + + + Contact ID for + Description of text box containing a contact's contact id for accessibility tech like screen readers + + + + + + Unknown user + A placeholder name for a contact whose name we do not know + + + + + Rename + Label for button which allows renaming of a contact + Ändra namn + + + + Renames this contact + Description of button which renames a contact for accessibility tech like screen readers + + + + + Remove + Label for button which removes a contact from the contact list + Ta bort + + + + Removes this contact + Description of button which removes a user from the contact list for accessibility tech like screen readers + + + + + Preferences for contact + Description of the contents of the 'Contacts' window for accessibility tech like screen readers + + + + + Contact Preferences Window + Name of the contact preferences window for accessibility tech like screen readers + + + + + A list of all your contacts, with their ricochet IDs, and options such as renaming and removing + Description of what user can do in the contact preferences window for accessibility tech like screen readers + + + + + ContactRequestDialog + + + Someone new is asking to connect to you + Descriptive text that is displayed in a popup window when a user tries to add you as a contact + Någon ber att få kontakt med dig + + + + Reject + Label for button which rejects a contact request when pressed + Avböj + + + + Rejects the incoming contact request + Description of what 'Reject' button does for accessibility tech like screen readers + + + + + Accept + Label for button which accepts a contact request when pressed + Acceptera + + + + Accepts the incoming contact request + Description of what 'Accept' button does for accessibility tech like screen readers + + + + + ContactRequestFields + + + ID: + Label for the contact id text box in the 'add new contact' window + ID: + + + + Name: + Label for the contact nickname text box in the 'add new contact' window + Namn: + + + + Field for the contact's nickname + Description of textbox for setting a contact's nickname for accessibility tech like screen readers + + + + + Message: + Label for the contact greeting message text box in the 'add new contact' window + Meddelande: + + + + Field for the contact's greeting message + Description of textbox for setting a new contact's initial greeting message for accessibility tech like screen readers + + + + + GeneralPreferences + + + Use a single window for conversations + Text description of an option to use one single program window for the contact list and the chats + Använd bara ett fönster för konversationer + + + Open links in default browser without prompting + Text description of an option to open URLs in default web browser when clicked + Öppna länkar i standardwebbläsaren utan att fråga + + + + Play audio notifications + Text description of an option to play audio notifications when contacts log in, log out, and send messages + Spela ljudnotiser + + + + + Volume + Label for a slider used to adjust audio notification volume +---------- +Name of the slider used to adjust audio notification volume for accessibility tech like screen readers + Volym + + + + + Language + Label for combobox where users can specify the UI language +---------- +Name of the combobox used to select UI langauge for accessibility tech like screen readers + Språk + + + + Restart Ricochet to apply changes + Starta om Ricochet för att aktivera ändringarna + + + + What language ricochet will use + Description of what the language combox is for for accessibility tech like screen readers + + + + + LanguagePreferences + + Select Language + Välj språk + + + Restart Ricochet to apply changes + Starta om Ricochet för att aktivera ändringarna + + + + LanguagesModel + + + System default + Systemets standardval + + + + Main + + + Ricochet Error + Ricochet fel + + + + MainToolBar + + + + Add Contact + Tooltip text for the button that launches the dialog box for adding a new contact +---------- +Name of the button for adding a new contact for accessibility tech like screen readers + Lägg till kontakt + + + + + Preferences + Tooltip text for the button that launches program preferences window +---------- +Name of the button for launching the preferences window for accessibility tech like screen readres + Inställningar + + + + Click to add contacts + Tooltip that displays on first launch indicating how to add a new contact + Klicka för att lägga till kontakter + + + + Shows the add contact dialogue + Description of the 'Add Contact' button for accessibility tech like screen readers + + + + + Shows the preferences dialogue + Description of the 'Preferences' button for accessibility tech like screen readers + + + + + MainWindow + + + Version Seperator + + + + + Remove %1 + Ta bort %1 + + + + Do you want to permanently remove %1? + Vill du ta bort %1 permanent? + + + + Main Toolbar + Name of the main toolbar for accessibility tech like screen readers + + + + + Toolbar with connection status, add contact button, and preferences button + Description of the main toolbar for accessibility tech like screen readers + + + + + Contact pane + Name of the pane holding the user's contacts for accessibility tech like screen readers + + + + + MessageDelegate + + + %1 is offline + %1 nickname + %1 är offline + + + + File transfer file name + Description of the text displaying the filename of a file transfer, used by accessibility tech like screen readres + + + + + File transfer progress + Description of progress bar displaying the file transfer progress, used by accessibility tech like screen readers + + + + + File transfer status + Description of label displaying the current status of a file transfer, used by accessibility tech like screen readers + + + + + Download + Label for file transfer 'Download' button for accessibility tech like screen readers + + + + + Download file + Description of what the file transfer 'Download' button does for accessibility tech like screen readers + + + + + Cancel or reject + Label for file transfer 'Cancel' button for accessibility tech like screen readers + + + + + Cancels or rejects a file transfer + Description of what the file transfer 'Cancel' button does for accessibility tech like screen readers + + + + Copy ID + Text for context menu command to copy a ricochet contact id to clipboard + Kopiera ID + + + Copy Link + Text for context menu command to copy a url to the clipboard + Kopiera länk + + + Open with Browser + Text for context menu command to open a url in a web browser + Öppna med webbläsare + + + Add as Contact + Lägg till som kontakt + + + + Copy Message + Text for context menu command to copy an entire message to clipboard + Kopiera meddelande + + + + Copy Selection + Text for context menu command to copy selected text to clipboard + Kopiera markering + + + + MessageDialogWrapper + + + Remove %1 + A command to remove a contact from the contact list, %1 is the contact's nickname + Ta bort %1 + + + + Do you want to permanently remove %1? + Confirmation of contact removal, %1 is the contact's nickname + Vill du ta bort %1 permanent? + + + + This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. + Message describing what will happen when a contact is removed from you contact list + Den här kontakten kommer inte längre att kunna skicka meddelanden till dig, och kommer att bli informerad om borttagningen. Kontakten kan välja att skicka en ny förfrågan. + + + + NetworkSetupWizard + + + This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. + A label with directions for when to use the 'Connect' button + Den här datorn kan ansluta till Internet obehindrat. Jag vill ansluta direkt till Tor-nätverket. + + + + Connect + Label for button to connect to the Tor network + Anslut + + + + This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. + A label with directions for when to use the 'Configure' button + Den här datorns Internetanslutning är censurerad, filtrerad eller kräver användning av en proxy. Jag behöver konfigurera nätverksinställningarna. + + + + Configure + Label for button to configure the Tor daemon beore connecting to the Tor network + Konfigurera + + + + OfflineStateItem + + + Configure + Button label + Konfigurera + + + + Details + Button label + Detaljer + + + + Connection failed + Label displayed when connecting to the Tor network fails + Anslutning misslyckades + + + + Connecting… + Label displayed when in process of connecting, … is ellipsis + Ansluter... + + + + OpenBrowserDialog + + <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. + Label displayed when user clicks on a link + <b>Varning!</b> Att öppna länkar med din standard&shy;webbläsare skadar din säkerhet och anonymitet.<br><br>Du kan <a href='.'>kopiera till Urklipp</a> istället. + + + Don't ask again for links from %1 + Fråga inte igen för länkar från %1 + + + Don't ask again for any links (not recommended!) + Checkbox option text for when user clicks on a link + Fråga inte igen för några länkar (rekommenderas inte!) + + + Open Browser + Label on button to open link in a web browser + Öppna webbläsaren + + + Cancel + Label on cancel button + Avbryt + + + + PreferencesDialog + + + Ricochet Preferences + Inställningar för Ricochet + + + + Menu Tabs + Name of the tab bar for accessibility tech like screen readers + + + + + General + Title of the general settings tab + Allmänt + + + Language + Språk + + + + Contacts + Title of the contacts list tab + Kontakter + + + + Tor + Title of the tor tab, contains tor settings and logs + Tor + + + + About + Title of the about tab, contains license information and ricochet version + Om + + + + QCocoaMenuItem + + + Preference + Inställning + + + + StartupStatusPage + + + The Tor process was not started successfully. This is most likely an installation or system error. + Error status string displayed when tor daemon does not launch successfully + Tor-processen startade inte korrekt. Det beror förmodligen på ett installations- eller systemfel. + + + + Quit + Button title to quit/terminate the program + Avsluta + + + + TorBootstrapStatus + + + Connecting to the Tor network… + Text status displayed when tor daemon is connecting, … is ellipsis + Ansluter till Tor-nätverket... + + + + Connection status + Description of the connection status text, used by accessibility tech like screen readers + + + + + Connection progress + Description of the bootstrap progress bar, used by accessibility tech like screen readers + + + + + + Back + Label for button which moves us back to previous screen + Tillbaka + + + + Hide Details + Label for button which hides the tor logs + + + + + Show Details + Label for button which shows the tor logs + + + + + Hides the tor progress log + Description of hide details button, used by accessibility tech like screen readers + + + + + Shows the tor progress log + Description of show details button, used by accessibility tech like screen readers + + + + Hide details + Göm detaljer + + + Show details + Visa detaljer + + + + Done + Label for button which closes the tor connection window + Klar + + + + TorConfigurationPage + + + Does this computer need a proxy to access the internet? + Behöver den här datorn använda en proxy för att ansluta till Internet? + + + + Proxy type: + Typ av proxy: + + + + + None + Ingen + + + + If you need a proxy to access the internet, select one from this list. + Description used by accessibility tech, such as screen readers + + + + + Address: + Label indicating the textbox to place a proxy IP or URL + Adress: + + + + IP address or hostname + Placeholder text of text box expecting an IP or URL for proxy + IP-adress eller värdnamn + + + + Enter the IP address or hostname of the proxy you wish to connect to + Description of what to enter into the IP textbox, used by accessibility tech such as screen readers + + + + + Port: + Label indicating the textbox to place a proxy port + Port: + + + + Port + Name of the port label, used by accessibility tech such as screen readers + + + + + Enter the port of the proxy you wish to connect to + Description of what to enter into the Port textbox, used by accessibility tech such as screen readers + + + + + Username: + Label indicating the textbox to place the proxy username + Användarnamn: + + + + + Optional + Textbox placeholder text indicating the field is not required + Krävs ej + + + + Username + Name of the username label, used by accessibility tech such as screen readers + + + + + If required, enter the username for the proxy you wish to connect to + Description to enter into the Username textbox, used by accessibility tech such as screen readers + + + + + Password: + Label indicating the textbox to place the proxy password + Lösenord: + + + + Password + Name of the password label, used by accessibility tech such as screen readers + + + + + If required, enter the password for the proxy you wish to connect to + Description to enter into the Password textbox, used by accessibility tech such as screen readers + + + + + Does this computer's Internet connection go through a firewall that only allows connections to certain ports? + Description for the purpose of the Allowed Ports textbox + Går den här datorns Internetanslutning genom en brandvägg som bara tillåter anslutningar på vissa portar? + + + + Allowed ports: + Label indicating the textbox to place the allowed ports + Tillåtna portar: + + + + Example: 80,443 + Textbox showing an example entry for the firewall allowed ports entry + Exempel: 80,443 + + + + Allowed ports + Name of the allowed ports label, used by accessibility tech such as screen readers + + + + + If this computer's Internet connection is censored, you will need to obtain and use bridge relays. + Om den här datorns Internetanslutning är censurerad måste du skaffa och använda en brygga till Tor, en så kallad ”bridge relay”. + + + + + Enter one or more bridge relays (one per line): + Ange en eller flera bryggor (en per rad): + + + + Back + Button label for going back to previous screen + Tillbaka + + + + Connect + Button label for connecting to tor + Anslut + + + + TorLogDisplay + + + Tor log + Name of the text field containg the tor logs, used by accessibility tech such as screen readers + + + + + TorPreferences + + + Running: + Display label that beside it indicates whether tor is running + Körs: + + + + Running + + + + + Whether tor is running + Description of the value of this label, used by acccessibility tech like screen readers + + + + + + Yes + Ja + + + + + No + Nej + + + External + Extern + + + + Control connected: + Display label that beside it indicates whether ricochet is connected to tor's control port + Kontroll ansluten: + + + + Control connected + + + + + Whether tor control connected + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Circuits established: + Kretsar etablerade: + + + + Circuits established + + + + + Whether circuits established + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Hidden service: + Dold tjänst: + + + + Hidden service + + + + + Online + Online + + + + Offline + Offline + + + + Whether a hidden service is up or not + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Version: + Version: + + + + Version + + + + + Error: <b>%1</b> + %1 is error message + Fel: <b>%1</b> + + + + Configure + Konfigurera + + + + TorStateWidget + + + + Connection failed + Anslutning misslyckades + + + + + Connecting… + … is ellipsis + Ansluter... + + + + Connecting… (%1%) + %1 is progress percentage, e.g. 100 + Ansluter… (%1%) + + + + Online + Online + + + + Connected + Ansluten + + + + Tor status + + + + + shims::ConversationModel + + + Pending + + + + + Accepted + Godkänd + + + + Rejected + Nekad + + + + Cancelled + + + + + Complete + + + + + Unkown Failure + + + + + Bad File Hash + + + + + Network Error + + + + + File System Error + + + + + Invalid + + + + + + Open File + + + + + + + + me + + + + + Save File + + + + diff --git a/src/ricochet-refresh/translation/ricochet_tr.ts b/src/ricochet-refresh/translation/ricochet_tr.ts new file mode 100644 index 00000000..e8ccffaa --- /dev/null +++ b/src/ricochet-refresh/translation/ricochet_tr.ts @@ -0,0 +1,1257 @@ + + + + + AboutPreferences + + Ricochet %1 + %1 version, e.g. 1.0.0 + Ricochet %1 + + + + Ricochet-Refresh %1 + %1 version, e.g. 1.0.0 + + + + + Ricochet Refresh version %1 + provides a readable interpretation of the Ricochet-Refresh version number for accessibility tech like screen readers + + + + + The license of Ricochet Refresh and its dependencies + + + + + License + + + + + About + Hakkında + + + + About page, contains license and version information + summary of the window's contents for accessibility tech like screen readers + + + + + Current Ricochet-Refresh version + + + + + AddContactDialog + + + Share your Ricochet ID to allow connection requests + tells the user the purpose of their Ricochet ID, which is basically a username + Bağlantı isteklerine izin vermek için Ricochet kimliğinizi paylaşın + + + + Cancel + label for button which dismisses a dialog + İptal + + + + Closes the contact add window + description for 'Cancel' button accessibility tech like screen readers + + + + + Add + button label to finish adding a contact/friend + Ekle + + + + Adds the contact to your contact list + description for 'Add' button for accessibility tech like screen readres + + + + + ChatPage + + + Message area + label for accessibility tech like screen readers + + + + + Write the message to be sent here. Press enter to send + description of the text area used to send messages for accessibility tech like screen readers + + + + + ContactActions + + + Warning + + + + + Could not successfully export conversation + + + + + Open Window + Context menu command to open the chat screen in a separate window + Yeni Pencere + + + + Details... + Context menu command to open a window showing the selected contact's details + Ayrıntılar... + + + + Rename + Context menu command to rename the selected contact + Yeniden adlandır + + + + Send File... + Context menu command to initiate a file transfer, opens a system file dialog + + + + + Export Conversation + Context menu command to initiate a chat log export, opens a system file dialog to export to + + + + + Remove + Context menu command to remove a contact from the contact list + Çıkar + + + + Contact options + Description of the items in the context menu for accessibility tech like screen readers + + + + + ContactIDField + + + <b>%1</b> is already your contact + Error message showed when user attempts to add a contact already in their contact list + <b>%1</b> kişi listenize zaten eklenmiş + + + + This ID is invalid + Error message showed when the id doesn't comply with spec https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt + + + + + You can't add yourself as a contact + Error message showed when user attempts to add themselves as a contact in their contact list + Kendinizi kişi listesine ekleyemezsiniz + + + + Enter an ID starting with <b>ricochet:</b> + Error message showed when the provided ricochet id is invalid + <b>ricochet:</b> ile başlayan bir kimlik girin + + + + Copied to clipboard + Message displayed when text is copied to the user's clipboard + Panoya kopyalandı + + + + Copy + Text displayed on a button used to copy somethign to the user's clipboard + Kopyala + + + + Copies the ricochet id to the clipboard + Text description of ricochet id copy button for accessibility tech like screen readers + + + + + ContactList + + + Online + Section header in the contact list for users which are online + Çevrimiçi + + + + Offline + Section header in the contact list for users which are offline + Çevrimdışı + + + + Requests + Section header in the contact list for users requesting to be added to the user's contact list + İstekler + + + + Rejected + Section header in the contact list for users that have rejected the user's request to be added to their contact list + Reddedildi + + + + Status for the given contacts + Description of the section header for accessibility tech like screen readers + + + + Outdated + Güncel Değil + + + + ContactPreferences + + Date added: + Eklenme tarihi: + + + Last seen: + Son görülme tarihi: + + + Request: + İstek: + + + Pending connection + Beklemedeki bağlantı + + + Delivered + Ulaştırıldı + + + Accepted + Kabul edildi + + + Error + Hata + + + Rejected + Reddedildi + + + %1 (Connected) + %1 status, e.g. "Accepted" + %1 (Bağlandı) + + + Response: + Cevap: + + + + Contact list + Description of the list of contacts for accessibility tech like screen readers + + + + + Contact ID for + Description of text box containing a contact's contact id for accessibility tech like screen readers + + + + + + Unknown user + A placeholder name for a contact whose name we do not know + + + + + Rename + Label for button which allows renaming of a contact + Yeniden adlandır + + + + Renames this contact + Description of button which renames a contact for accessibility tech like screen readers + + + + + Remove + Label for button which removes a contact from the contact list + Çıkar + + + + Removes this contact + Description of button which removes a user from the contact list for accessibility tech like screen readers + + + + + Preferences for contact + Description of the contents of the 'Contacts' window for accessibility tech like screen readers + + + + + Contact Preferences Window + Name of the contact preferences window for accessibility tech like screen readers + + + + + A list of all your contacts, with their ricochet IDs, and options such as renaming and removing + Description of what user can do in the contact preferences window for accessibility tech like screen readers + + + + + ContactRequestDialog + + + Someone new is asking to connect to you + Descriptive text that is displayed in a popup window when a user tries to add you as a contact + Yeni bir kişi sizinle bağlantı kurmak istiyor + + + + Reject + Label for button which rejects a contact request when pressed + Reddet + + + + Rejects the incoming contact request + Description of what 'Reject' button does for accessibility tech like screen readers + + + + + Accept + Label for button which accepts a contact request when pressed + Kabul et + + + + Accepts the incoming contact request + Description of what 'Accept' button does for accessibility tech like screen readers + + + + + ContactRequestFields + + + ID: + Label for the contact id text box in the 'add new contact' window + Kimlik: + + + + Name: + Label for the contact nickname text box in the 'add new contact' window + Ad: + + + + Field for the contact's nickname + Description of textbox for setting a contact's nickname for accessibility tech like screen readers + + + + + Message: + Label for the contact greeting message text box in the 'add new contact' window + Mesaj: + + + + Field for the contact's greeting message + Description of textbox for setting a new contact's initial greeting message for accessibility tech like screen readers + + + + + GeneralPreferences + + + Use a single window for conversations + Text description of an option to use one single program window for the contact list and the chats + Görüşmeler için tek pencere kullan + + + Open links in default browser without prompting + Text description of an option to open URLs in default web browser when clicked + Bağlantıları bana sormadan varsayılan tarayıcıda aç + + + + Play audio notifications + Text description of an option to play audio notifications when contacts log in, log out, and send messages + Ses bildirimlerini oynat + + + + + Volume + Label for a slider used to adjust audio notification volume +---------- +Name of the slider used to adjust audio notification volume for accessibility tech like screen readers + Ses + + + + + Language + Label for combobox where users can specify the UI language +---------- +Name of the combobox used to select UI langauge for accessibility tech like screen readers + Dil + + + + Restart Ricochet to apply changes + Değişikliklerin uygulanması için Ricochet'i yeniden başlatın. + + + + What language ricochet will use + Description of what the language combox is for for accessibility tech like screen readers + + + + + LanguagePreferences + + Select Language + Dil Seçin + + + Restart Ricochet to apply changes + Değişikliklerin uygulanması için Ricochet'i yeniden başlatın. + + + + LanguagesModel + + + System default + Sistem varsayılanı + + + + Main + + + Ricochet Error + Ricochet Hatası + + + + MainToolBar + + + + Add Contact + Tooltip text for the button that launches the dialog box for adding a new contact +---------- +Name of the button for adding a new contact for accessibility tech like screen readers + Kişi Ekle + + + + + Preferences + Tooltip text for the button that launches program preferences window +---------- +Name of the button for launching the preferences window for accessibility tech like screen readres + Ayarlar + + + + Click to add contacts + Tooltip that displays on first launch indicating how to add a new contact + Kişi eklemek için tıklayın + + + + Shows the add contact dialogue + Description of the 'Add Contact' button for accessibility tech like screen readers + + + + + Shows the preferences dialogue + Description of the 'Preferences' button for accessibility tech like screen readers + + + + + MainWindow + + + Version Seperator + + + + + Remove %1 + %1 isimli kullanıcıyı kişi listesinden çıkar + + + + Do you want to permanently remove %1? + %1 isimli kullanıcıyı kalıcı olarak silmek istiyor musunuz? + + + + Main Toolbar + Name of the main toolbar for accessibility tech like screen readers + + + + + Toolbar with connection status, add contact button, and preferences button + Description of the main toolbar for accessibility tech like screen readers + + + + + Contact pane + Name of the pane holding the user's contacts for accessibility tech like screen readers + + + + + MessageDelegate + + + %1 is offline + %1 nickname + %1 çevrimdışı + + + + File transfer file name + Description of the text displaying the filename of a file transfer, used by accessibility tech like screen readres + + + + + File transfer progress + Description of progress bar displaying the file transfer progress, used by accessibility tech like screen readers + + + + + File transfer status + Description of label displaying the current status of a file transfer, used by accessibility tech like screen readers + + + + + Download + Label for file transfer 'Download' button for accessibility tech like screen readers + + + + + Download file + Description of what the file transfer 'Download' button does for accessibility tech like screen readers + + + + + Cancel or reject + Label for file transfer 'Cancel' button for accessibility tech like screen readers + + + + + Cancels or rejects a file transfer + Description of what the file transfer 'Cancel' button does for accessibility tech like screen readers + + + + Copy ID + Text for context menu command to copy a ricochet contact id to clipboard + Kimliği kopyala + + + Copy Link + Text for context menu command to copy a url to the clipboard + Bağlantı Kopyala + + + Open with Browser + Text for context menu command to open a url in a web browser + Tarayıcı ile Aç + + + Add as Contact + Kişi olarak Ekle + + + + Copy Message + Text for context menu command to copy an entire message to clipboard + Mesajı Kopyala + + + + Copy Selection + Text for context menu command to copy selected text to clipboard + Seçileni Kopyala + + + + MessageDialogWrapper + + + Remove %1 + A command to remove a contact from the contact list, %1 is the contact's nickname + %1 isimli kullanıcıyı sil + + + + Do you want to permanently remove %1? + Confirmation of contact removal, %1 is the contact's nickname + %1 isimli kullanıcıyı kalıcı olarak silmek istiyor musunuz? + + + + This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. + Message describing what will happen when a contact is removed from you contact list + Bu kişi artık size mesaj gönderemeyecek ve silinme işlemiyle ilgili bilgilendirilecek. Kişi isterse size yeni bir bağlantı isteği gönderebilir. + + + + NetworkSetupWizard + + + This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. + A label with directions for when to use the 'Connect' button + Bu bilgisayarın internete bağlanmasında bir engel yoktur. Doğrudan Tor ağına bağlanmak istiyorum. + + + + Connect + Label for button to connect to the Tor network + Bağlan + + + + This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. + A label with directions for when to use the 'Configure' button + Bu bilgisayarın internet bağlantısı sansürlenmiş, filtrelenmiş ya da proxy kullanmaktadır. Ağ ayarlarını yapılandırmam gerekiyor. + + + + Configure + Label for button to configure the Tor daemon beore connecting to the Tor network + Yapılandır + + + + OfflineStateItem + + + Configure + Button label + Yapılandır + + + + Details + Button label + Ayrıntılar + + + + Connection failed + Label displayed when connecting to the Tor network fails + Bağlantı başarısız + + + + Connecting… + Label displayed when in process of connecting, … is ellipsis + Bağlanıyor... + + + + OpenBrowserDialog + + <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. + Label displayed when user clicks on a link + <b>Uyarı!</b> Varsayılan tarayıcı ile bağlantıları açmak, güvenlik ve anonimliğinize zarar verecektir.<br><br>Bunun yerine <a href='.'>panoya kopyala</a>yabilirsiniz. + + + Don't ask again for links from %1 + %1 isimli kullanıcıdan gelen bağlantılar için tekrar sorma + + + Don't ask again for any links (not recommended!) + Checkbox option text for when user clicks on a link + Bundan sonra bağlantılar için bir daha sorma (tavsiye edilmez!) + + + Open Browser + Label on button to open link in a web browser + Tarayıcıyı Aç + + + Cancel + Label on cancel button + İptal + + + + PreferencesDialog + + + Ricochet Preferences + Ricochet Ayarları + + + + Menu Tabs + Name of the tab bar for accessibility tech like screen readers + + + + + General + Title of the general settings tab + Genel + + + Language + Dil + + + + Contacts + Title of the contacts list tab + Kişiler + + + + Tor + Title of the tor tab, contains tor settings and logs + Tor + + + + About + Title of the about tab, contains license information and ricochet version + Hakkında + + + + QCocoaMenuItem + + + Preference + Tercih + + + + StartupStatusPage + + + The Tor process was not started successfully. This is most likely an installation or system error. + Error status string displayed when tor daemon does not launch successfully + Tor başlatılamadı. Büyük olasılıkla kurulum veya sistem hatası. + + + + Quit + Button title to quit/terminate the program + Çık + + + + TorBootstrapStatus + + + Connecting to the Tor network… + Text status displayed when tor daemon is connecting, … is ellipsis + Tor ağına bağlanıyor... + + + + Connection status + Description of the connection status text, used by accessibility tech like screen readers + + + + + Connection progress + Description of the bootstrap progress bar, used by accessibility tech like screen readers + + + + + + Back + Label for button which moves us back to previous screen + Geri + + + + Hide Details + Label for button which hides the tor logs + + + + + Show Details + Label for button which shows the tor logs + + + + + Hides the tor progress log + Description of hide details button, used by accessibility tech like screen readers + + + + + Shows the tor progress log + Description of show details button, used by accessibility tech like screen readers + + + + Hide details + Ayrıntıları gizle + + + Show details + Ayrıntıları göster + + + + Done + Label for button which closes the tor connection window + Tamamla + + + + TorConfigurationPage + + + Does this computer need a proxy to access the internet? + Bu bilgisayar internete erişmek için bir proxy'e ihtiyaç duyuyor mu? + + + + Proxy type: + Proxy türü: + + + + + None + Hiçbiri + + + + If you need a proxy to access the internet, select one from this list. + Description used by accessibility tech, such as screen readers + + + + + Address: + Label indicating the textbox to place a proxy IP or URL + Adres: + + + + IP address or hostname + Placeholder text of text box expecting an IP or URL for proxy + IP adresi veya host adı + + + + Enter the IP address or hostname of the proxy you wish to connect to + Description of what to enter into the IP textbox, used by accessibility tech such as screen readers + + + + + Port: + Label indicating the textbox to place a proxy port + Port: + + + + Port + Name of the port label, used by accessibility tech such as screen readers + + + + + Enter the port of the proxy you wish to connect to + Description of what to enter into the Port textbox, used by accessibility tech such as screen readers + + + + + Username: + Label indicating the textbox to place the proxy username + Kullanıcı adı: + + + + + Optional + Textbox placeholder text indicating the field is not required + İsteğe bağlı + + + + Username + Name of the username label, used by accessibility tech such as screen readers + + + + + If required, enter the username for the proxy you wish to connect to + Description to enter into the Username textbox, used by accessibility tech such as screen readers + + + + + Password: + Label indicating the textbox to place the proxy password + Parola: + + + + Password + Name of the password label, used by accessibility tech such as screen readers + + + + + If required, enter the password for the proxy you wish to connect to + Description to enter into the Password textbox, used by accessibility tech such as screen readers + + + + + Does this computer's Internet connection go through a firewall that only allows connections to certain ports? + Description for the purpose of the Allowed Ports textbox + Bu bilgisayarın internet bağlantısı sadece belli portlara bağlantı izni veren bir güvenlik duvarı üzerinden gidiyor mu? + + + + Allowed ports: + Label indicating the textbox to place the allowed ports + İzin verilen portlar: + + + + Example: 80,443 + Textbox showing an example entry for the firewall allowed ports entry + Örnek: 80.443 + + + + Allowed ports + Name of the allowed ports label, used by accessibility tech such as screen readers + + + + + If this computer's Internet connection is censored, you will need to obtain and use bridge relays. + Bu bilgisayarın internet bağlantısı sansürlenmişse, köprü aktarımlarını edinmeniz ve kullanmanız gerekir. + + + + + Enter one or more bridge relays (one per line): + Bir veya birden fazla köprü aktarım adresi girin (satır başına bir tane): + + + + Back + Button label for going back to previous screen + Geri + + + + Connect + Button label for connecting to tor + Bağlan + + + + TorLogDisplay + + + Tor log + Name of the text field containg the tor logs, used by accessibility tech such as screen readers + + + + + TorPreferences + + + Running: + Display label that beside it indicates whether tor is running + Çalışıyor: + + + + Running + + + + + Whether tor is running + Description of the value of this label, used by acccessibility tech like screen readers + + + + + + Yes + Evet + + + + + No + Hayır + + + External + Harici + + + + Control connected: + Display label that beside it indicates whether ricochet is connected to tor's control port + Kontrol bağlantısı: + + + + Control connected + + + + + Whether tor control connected + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Circuits established: + Devrelere bağlanıldı: + + + + Circuits established + + + + + Whether circuits established + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Hidden service: + Gizli servis: + + + + Hidden service + + + + + Online + Çevrimiçi + + + + Offline + Çevrimdışı + + + + Whether a hidden service is up or not + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Version: + Sürüm: + + + + Version + + + + + Error: <b>%1</b> + %1 is error message + Hata: <b>%1</b> + + + + Configure + Yapılandır + + + + TorStateWidget + + + + Connection failed + Bağlantı başarısız + + + + + Connecting… + … is ellipsis + Bağlanıyor... + + + + Connecting… (%1%) + %1 is progress percentage, e.g. 100 + Bağlanıyor... (%1%) + + + + Online + Çevrimiçi + + + + Connected + Bağlı + + + + Tor status + + + + + shims::ConversationModel + + + Pending + + + + + Accepted + Kabul edildi + + + + Rejected + Reddedildi + + + + Cancelled + + + + + Complete + + + + + Unkown Failure + + + + + Bad File Hash + + + + + Network Error + + + + + File System Error + + + + + Invalid + + + + + + Open File + + + + + + + + me + + + + + Save File + + + + diff --git a/src/ricochet-refresh/translation/ricochet_uk.ts b/src/ricochet-refresh/translation/ricochet_uk.ts new file mode 100644 index 00000000..22336b59 --- /dev/null +++ b/src/ricochet-refresh/translation/ricochet_uk.ts @@ -0,0 +1,1257 @@ + + + + + AboutPreferences + + Ricochet %1 + %1 version, e.g. 1.0.0 + Ricochet %1 + + + + Ricochet-Refresh %1 + %1 version, e.g. 1.0.0 + + + + + Ricochet Refresh version %1 + provides a readable interpretation of the Ricochet-Refresh version number for accessibility tech like screen readers + + + + + The license of Ricochet Refresh and its dependencies + + + + + License + + + + + About + Про + + + + About page, contains license and version information + summary of the window's contents for accessibility tech like screen readers + + + + + Current Ricochet-Refresh version + + + + + AddContactDialog + + + Share your Ricochet ID to allow connection requests + tells the user the purpose of their Ricochet ID, which is basically a username + Поділитись вашим Ricochet ID для запитів на підключення + + + + Cancel + label for button which dismisses a dialog + Відмінити + + + + Closes the contact add window + description for 'Cancel' button accessibility tech like screen readers + + + + + Add + button label to finish adding a contact/friend + Додати + + + + Adds the contact to your contact list + description for 'Add' button for accessibility tech like screen readres + + + + + ChatPage + + + Message area + label for accessibility tech like screen readers + + + + + Write the message to be sent here. Press enter to send + description of the text area used to send messages for accessibility tech like screen readers + + + + + ContactActions + + + Warning + + + + + Could not successfully export conversation + + + + + Open Window + Context menu command to open the chat screen in a separate window + Відкрити вікно + + + + Details... + Context menu command to open a window showing the selected contact's details + Детальніше... + + + + Rename + Context menu command to rename the selected contact + Перейменувати + + + + Send File... + Context menu command to initiate a file transfer, opens a system file dialog + + + + + Export Conversation + Context menu command to initiate a chat log export, opens a system file dialog to export to + + + + + Remove + Context menu command to remove a contact from the contact list + Видалити + + + + Contact options + Description of the items in the context menu for accessibility tech like screen readers + + + + + ContactIDField + + + <b>%1</b> is already your contact + Error message showed when user attempts to add a contact already in their contact list + <b>%1</b> це вже ваш контакт + + + + This ID is invalid + Error message showed when the id doesn't comply with spec https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt + + + + + You can't add yourself as a contact + Error message showed when user attempts to add themselves as a contact in their contact list + Ви не можете додати самі себе як контакт + + + + Enter an ID starting with <b>ricochet:</b> + Error message showed when the provided ricochet id is invalid + Введіть ID який починається з <b>ricochet:</b> + + + + Copied to clipboard + Message displayed when text is copied to the user's clipboard + Скопійовано до буферу обміну + + + + Copy + Text displayed on a button used to copy somethign to the user's clipboard + Копіювати + + + + Copies the ricochet id to the clipboard + Text description of ricochet id copy button for accessibility tech like screen readers + + + + + ContactList + + + Online + Section header in the contact list for users which are online + В мережі + + + + Offline + Section header in the contact list for users which are offline + Не в мережі + + + + Requests + Section header in the contact list for users requesting to be added to the user's contact list + Запити + + + + Rejected + Section header in the contact list for users that have rejected the user's request to be added to their contact list + Відхилено + + + + Status for the given contacts + Description of the section header for accessibility tech like screen readers + + + + Outdated + Застарілий + + + + ContactPreferences + + Date added: + Дата додавання: + + + Last seen: + Останній раз бачили: + + + Request: + Запит: + + + Pending connection + Встановлення з'єднання + + + Delivered + Доставлено + + + Accepted + Прийнято + + + Error + Помилка + + + Rejected + Відхилено + + + %1 (Connected) + %1 status, e.g. "Accepted" + %1 (Підключено) + + + Response: + Відповідь: + + + + Contact list + Description of the list of contacts for accessibility tech like screen readers + + + + + Contact ID for + Description of text box containing a contact's contact id for accessibility tech like screen readers + + + + + + Unknown user + A placeholder name for a contact whose name we do not know + + + + + Rename + Label for button which allows renaming of a contact + Перейменувати + + + + Renames this contact + Description of button which renames a contact for accessibility tech like screen readers + + + + + Remove + Label for button which removes a contact from the contact list + Видалити + + + + Removes this contact + Description of button which removes a user from the contact list for accessibility tech like screen readers + + + + + Preferences for contact + Description of the contents of the 'Contacts' window for accessibility tech like screen readers + + + + + Contact Preferences Window + Name of the contact preferences window for accessibility tech like screen readers + + + + + A list of all your contacts, with their ricochet IDs, and options such as renaming and removing + Description of what user can do in the contact preferences window for accessibility tech like screen readers + + + + + ContactRequestDialog + + + Someone new is asking to connect to you + Descriptive text that is displayed in a popup window when a user tries to add you as a contact + Хтось новий запрошує підключення до вас + + + + Reject + Label for button which rejects a contact request when pressed + Відхилити + + + + Rejects the incoming contact request + Description of what 'Reject' button does for accessibility tech like screen readers + + + + + Accept + Label for button which accepts a contact request when pressed + Прийняти + + + + Accepts the incoming contact request + Description of what 'Accept' button does for accessibility tech like screen readers + + + + + ContactRequestFields + + + ID: + Label for the contact id text box in the 'add new contact' window + ID: + + + + Name: + Label for the contact nickname text box in the 'add new contact' window + Ім'я: + + + + Field for the contact's nickname + Description of textbox for setting a contact's nickname for accessibility tech like screen readers + + + + + Message: + Label for the contact greeting message text box in the 'add new contact' window + Повідомлення: + + + + Field for the contact's greeting message + Description of textbox for setting a new contact's initial greeting message for accessibility tech like screen readers + + + + + GeneralPreferences + + + Use a single window for conversations + Text description of an option to use one single program window for the contact list and the chats + Використовувати одне вікно для розмов + + + Open links in default browser without prompting + Text description of an option to open URLs in default web browser when clicked + Відкривати посилання в браузері без запитань + + + + Play audio notifications + Text description of an option to play audio notifications when contacts log in, log out, and send messages + Програти аудіо повідомлення + + + + + Volume + Label for a slider used to adjust audio notification volume +---------- +Name of the slider used to adjust audio notification volume for accessibility tech like screen readers + Гучність + + + + + Language + Label for combobox where users can specify the UI language +---------- +Name of the combobox used to select UI langauge for accessibility tech like screen readers + Мова + + + + Restart Ricochet to apply changes + Перезавантажити Ricochet для збереження змін + + + + What language ricochet will use + Description of what the language combox is for for accessibility tech like screen readers + + + + + LanguagePreferences + + Select Language + Вибір мови + + + Restart Ricochet to apply changes + Перезавантажити Ricochet для збереження змін + + + + LanguagesModel + + + System default + Системні налаштування + + + + Main + + + Ricochet Error + Помилка Ricochet + + + + MainToolBar + + + + Add Contact + Tooltip text for the button that launches the dialog box for adding a new contact +---------- +Name of the button for adding a new contact for accessibility tech like screen readers + Додати контакт + + + + + Preferences + Tooltip text for the button that launches program preferences window +---------- +Name of the button for launching the preferences window for accessibility tech like screen readres + Налаштування + + + + Click to add contacts + Tooltip that displays on first launch indicating how to add a new contact + Натисніть щоб додати контакти + + + + Shows the add contact dialogue + Description of the 'Add Contact' button for accessibility tech like screen readers + + + + + Shows the preferences dialogue + Description of the 'Preferences' button for accessibility tech like screen readers + + + + + MainWindow + + + Version Seperator + + + + + Remove %1 + Видалити %1 + + + + Do you want to permanently remove %1? + Бажаєте назавжди видалити %1? + + + + Main Toolbar + Name of the main toolbar for accessibility tech like screen readers + + + + + Toolbar with connection status, add contact button, and preferences button + Description of the main toolbar for accessibility tech like screen readers + + + + + Contact pane + Name of the pane holding the user's contacts for accessibility tech like screen readers + + + + + MessageDelegate + + + %1 is offline + %1 nickname + %1 не в мережі + + + + File transfer file name + Description of the text displaying the filename of a file transfer, used by accessibility tech like screen readres + + + + + File transfer progress + Description of progress bar displaying the file transfer progress, used by accessibility tech like screen readers + + + + + File transfer status + Description of label displaying the current status of a file transfer, used by accessibility tech like screen readers + + + + + Download + Label for file transfer 'Download' button for accessibility tech like screen readers + + + + + Download file + Description of what the file transfer 'Download' button does for accessibility tech like screen readers + + + + + Cancel or reject + Label for file transfer 'Cancel' button for accessibility tech like screen readers + + + + + Cancels or rejects a file transfer + Description of what the file transfer 'Cancel' button does for accessibility tech like screen readers + + + + Copy ID + Text for context menu command to copy a ricochet contact id to clipboard + Копіювати ID + + + Copy Link + Text for context menu command to copy a url to the clipboard + Копіювати посилання + + + Open with Browser + Text for context menu command to open a url in a web browser + Відкрити браузер + + + Add as Contact + Додати як контакт + + + + Copy Message + Text for context menu command to copy an entire message to clipboard + Скопіювати повідомлення + + + + Copy Selection + Text for context menu command to copy selected text to clipboard + Скопіювати вибране + + + + MessageDialogWrapper + + + Remove %1 + A command to remove a contact from the contact list, %1 is the contact's nickname + Видалити %1 + + + + Do you want to permanently remove %1? + Confirmation of contact removal, %1 is the contact's nickname + Бажаєте назавжди видалити %1? + + + + This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. + Message describing what will happen when a contact is removed from you contact list + Цей контакт більше не зможе вам писати і буде проінформований про видалення. Він може послати запит на нове з'єднання. + + + + NetworkSetupWizard + + + This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. + A label with directions for when to use the 'Connect' button + Це інтернет з'єднання не блокується провайдером. Я б хотів підключитись напряму до мережі Tor. + + + + Connect + Label for button to connect to the Tor network + Підключення + + + + This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. + A label with directions for when to use the 'Configure' button + Це інтернет з'єднання цензурується провайдером або фільтрується. Мені треба налаштувати параметри мережі. + + + + Configure + Label for button to configure the Tor daemon beore connecting to the Tor network + Налаштувати + + + + OfflineStateItem + + + Configure + Button label + Налаштувати + + + + Details + Button label + Детальніше + + + + Connection failed + Label displayed when connecting to the Tor network fails + З'єднання не вдалося. + + + + Connecting… + Label displayed when in process of connecting, … is ellipsis + З'єднання... + + + + OpenBrowserDialog + + <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. + Label displayed when user clicks on a link + <b>Попередження!</b> Відкриття посилань у браузері за замовчуванням може нашкодити вашій безпеці та анонімності. <br><br>Замість цього ви можете їх <a href='.'>скопіювати у буфер обміну</a> . + + + Don't ask again for links from %1 + Не запитувати знову про посилання від %1 + + + Don't ask again for any links (not recommended!) + Checkbox option text for when user clicks on a link + Не запитувати знову про будь-які посилання (не рекомендовано!) + + + Open Browser + Label on button to open link in a web browser + Відкрити браузер + + + Cancel + Label on cancel button + Відмінити + + + + PreferencesDialog + + + Ricochet Preferences + Налаштування Ricochet + + + + Menu Tabs + Name of the tab bar for accessibility tech like screen readers + + + + + General + Title of the general settings tab + Основні + + + Language + Мова + + + + Contacts + Title of the contacts list tab + Контакти + + + + Tor + Title of the tor tab, contains tor settings and logs + Tor + + + + About + Title of the about tab, contains license information and ricochet version + Про + + + + QCocoaMenuItem + + + Preference + Налаштування + + + + StartupStatusPage + + + The Tor process was not started successfully. This is most likely an installation or system error. + Error status string displayed when tor daemon does not launch successfully + Процес Tor не був вдало запущений. Скоріш за все це помилка при установці або системна. + + + + Quit + Button title to quit/terminate the program + Вийти + + + + TorBootstrapStatus + + + Connecting to the Tor network… + Text status displayed when tor daemon is connecting, … is ellipsis + З'єднання з мережею Tor... + + + + Connection status + Description of the connection status text, used by accessibility tech like screen readers + + + + + Connection progress + Description of the bootstrap progress bar, used by accessibility tech like screen readers + + + + + + Back + Label for button which moves us back to previous screen + Назад + + + + Hide Details + Label for button which hides the tor logs + + + + + Show Details + Label for button which shows the tor logs + + + + + Hides the tor progress log + Description of hide details button, used by accessibility tech like screen readers + + + + + Shows the tor progress log + Description of show details button, used by accessibility tech like screen readers + + + + Hide details + Сховати деталі + + + Show details + Показати деталі + + + + Done + Label for button which closes the tor connection window + Виконано + + + + TorConfigurationPage + + + Does this computer need a proxy to access the internet? + Чи потребує цей комп'ютер проксі для доступу до інтернету? + + + + Proxy type: + Тип проксі: + + + + + None + Ніякого + + + + If you need a proxy to access the internet, select one from this list. + Description used by accessibility tech, such as screen readers + + + + + Address: + Label indicating the textbox to place a proxy IP or URL + Адреса: + + + + IP address or hostname + Placeholder text of text box expecting an IP or URL for proxy + IP адреса чи ім'я сервера + + + + Enter the IP address or hostname of the proxy you wish to connect to + Description of what to enter into the IP textbox, used by accessibility tech such as screen readers + + + + + Port: + Label indicating the textbox to place a proxy port + Порт: + + + + Port + Name of the port label, used by accessibility tech such as screen readers + + + + + Enter the port of the proxy you wish to connect to + Description of what to enter into the Port textbox, used by accessibility tech such as screen readers + + + + + Username: + Label indicating the textbox to place the proxy username + Ім'я: + + + + + Optional + Textbox placeholder text indicating the field is not required + Опції + + + + Username + Name of the username label, used by accessibility tech such as screen readers + + + + + If required, enter the username for the proxy you wish to connect to + Description to enter into the Username textbox, used by accessibility tech such as screen readers + + + + + Password: + Label indicating the textbox to place the proxy password + Пароль: + + + + Password + Name of the password label, used by accessibility tech such as screen readers + + + + + If required, enter the password for the proxy you wish to connect to + Description to enter into the Password textbox, used by accessibility tech such as screen readers + + + + + Does this computer's Internet connection go through a firewall that only allows connections to certain ports? + Description for the purpose of the Allowed Ports textbox + З'єднання цього комп'ютеру проходять через firewall який дозволяй з'єднання тільки з певними портами? + + + + Allowed ports: + Label indicating the textbox to place the allowed ports + Дозволені порти: + + + + Example: 80,443 + Textbox showing an example entry for the firewall allowed ports entry + Наприклад: 80,443 + + + + Allowed ports + Name of the allowed ports label, used by accessibility tech such as screen readers + + + + + If this computer's Internet connection is censored, you will need to obtain and use bridge relays. + Якщо інтернет з'єднання цього комп'ютеру цензуруються, то вам потрібно отримати і використовувати ретранслятори. + + + + + Enter one or more bridge relays (one per line): + Введіть один або декілька адрес ретрансляторів (один на лінію) + + + + Back + Button label for going back to previous screen + Назад + + + + Connect + Button label for connecting to tor + Підключення + + + + TorLogDisplay + + + Tor log + Name of the text field containg the tor logs, used by accessibility tech such as screen readers + + + + + TorPreferences + + + Running: + Display label that beside it indicates whether tor is running + Запущено: + + + + Running + + + + + Whether tor is running + Description of the value of this label, used by acccessibility tech like screen readers + + + + + + Yes + Так + + + + + No + Ні + + + External + Зовнішній + + + + Control connected: + Display label that beside it indicates whether ricochet is connected to tor's control port + Контрольне з'єднання: + + + + Control connected + + + + + Whether tor control connected + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Circuits established: + Схема побудована: + + + + Circuits established + + + + + Whether circuits established + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Hidden service: + Прихований сервіс: + + + + Hidden service + + + + + Online + В мережі + + + + Offline + Не в мережі + + + + Whether a hidden service is up or not + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Version: + Версія: + + + + Version + + + + + Error: <b>%1</b> + %1 is error message + Помилка: <b>%1</b> + + + + Configure + Налаштувати + + + + TorStateWidget + + + + Connection failed + З'єднання не вдалося. + + + + + Connecting… + … is ellipsis + З'єднання... + + + + Connecting… (%1%) + %1 is progress percentage, e.g. 100 + З'єднання... (%1%) + + + + Online + В мережі + + + + Connected + Підключено + + + + Tor status + + + + + shims::ConversationModel + + + Pending + + + + + Accepted + Прийнято + + + + Rejected + Відхилено + + + + Cancelled + + + + + Complete + + + + + Unkown Failure + + + + + Bad File Hash + + + + + Network Error + + + + + File System Error + + + + + Invalid + + + + + + Open File + + + + + + + + me + + + + + Save File + + + + diff --git a/src/ricochet-refresh/translation/ricochet_zh.ts b/src/ricochet-refresh/translation/ricochet_zh.ts new file mode 100644 index 00000000..e650550c --- /dev/null +++ b/src/ricochet-refresh/translation/ricochet_zh.ts @@ -0,0 +1,1248 @@ + + + + + AboutPreferences + + Ricochet %1 + %1 version, e.g. 1.0.0 + Ricochet %1 + + + + Ricochet-Refresh %1 + %1 version, e.g. 1.0.0 + + + + + Ricochet Refresh version %1 + provides a readable interpretation of the Ricochet-Refresh version number for accessibility tech like screen readers + + + + + The license of Ricochet Refresh and its dependencies + + + + + License + + + + + About + 关于 + + + + About page, contains license and version information + summary of the window's contents for accessibility tech like screen readers + + + + + Current Ricochet-Refresh version + + + + + AddContactDialog + + + Share your Ricochet ID to allow connection requests + tells the user the purpose of their Ricochet ID, which is basically a username + 分享您的Ricochet ID给你的好友 + + + + Cancel + label for button which dismisses a dialog + 取消 + + + + Closes the contact add window + description for 'Cancel' button accessibility tech like screen readers + + + + + Add + button label to finish adding a contact/friend + 添加 + + + + Adds the contact to your contact list + description for 'Add' button for accessibility tech like screen readres + + + + + ChatPage + + + Message area + label for accessibility tech like screen readers + + + + + Write the message to be sent here. Press enter to send + description of the text area used to send messages for accessibility tech like screen readers + + + + + ContactActions + + + Warning + + + + + Could not successfully export conversation + + + + + Open Window + Context menu command to open the chat screen in a separate window + 打开窗口 + + + + Details... + Context menu command to open a window showing the selected contact's details + 细节…… + + + + Rename + Context menu command to rename the selected contact + 重命名 + + + + Send File... + Context menu command to initiate a file transfer, opens a system file dialog + + + + + Export Conversation + Context menu command to initiate a chat log export, opens a system file dialog to export to + + + + + Remove + Context menu command to remove a contact from the contact list + 移除 + + + + Contact options + Description of the items in the context menu for accessibility tech like screen readers + + + + + ContactIDField + + + <b>%1</b> is already your contact + Error message showed when user attempts to add a contact already in their contact list + <b>%1</b>已经是你的联系人 + + + + This ID is invalid + Error message showed when the id doesn't comply with spec https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt + + + + + You can't add yourself as a contact + Error message showed when user attempts to add themselves as a contact in their contact list + 不能添加自己为联系人 + + + + Enter an ID starting with <b>ricochet:</b> + Error message showed when the provided ricochet id is invalid + 输入一个ID并以<b>ricochet:</b>打头 + + + + Copied to clipboard + Message displayed when text is copied to the user's clipboard + 复制到剪贴板 + + + + Copy + Text displayed on a button used to copy somethign to the user's clipboard + 复制 + + + + Copies the ricochet id to the clipboard + Text description of ricochet id copy button for accessibility tech like screen readers + + + + + ContactList + + + Online + Section header in the contact list for users which are online + 在线 + + + + Offline + Section header in the contact list for users which are offline + 离线 + + + + Requests + Section header in the contact list for users requesting to be added to the user's contact list + 请求 + + + + Rejected + Section header in the contact list for users that have rejected the user's request to be added to their contact list + 已拒绝 + + + + Status for the given contacts + Description of the section header for accessibility tech like screen readers + + + + + ContactPreferences + + Date added: + 添加日期: + + + Last seen: + 最后上线: + + + Request: + 请求: + + + Pending connection + 挂起的连接 + + + Delivered + 送达 + + + Accepted + 已接受 + + + Error + 错误 + + + Rejected + 已拒绝 + + + %1 (Connected) + %1 status, e.g. "Accepted" + %1 (已连接) + + + Response: + 回复: + + + + Contact list + Description of the list of contacts for accessibility tech like screen readers + + + + + Contact ID for + Description of text box containing a contact's contact id for accessibility tech like screen readers + + + + + + Unknown user + A placeholder name for a contact whose name we do not know + + + + + Rename + Label for button which allows renaming of a contact + 重命名 + + + + Renames this contact + Description of button which renames a contact for accessibility tech like screen readers + + + + + Remove + Label for button which removes a contact from the contact list + 移除 + + + + Removes this contact + Description of button which removes a user from the contact list for accessibility tech like screen readers + + + + + Preferences for contact + Description of the contents of the 'Contacts' window for accessibility tech like screen readers + + + + + Contact Preferences Window + Name of the contact preferences window for accessibility tech like screen readers + + + + + A list of all your contacts, with their ricochet IDs, and options such as renaming and removing + Description of what user can do in the contact preferences window for accessibility tech like screen readers + + + + + ContactRequestDialog + + + Someone new is asking to connect to you + Descriptive text that is displayed in a popup window when a user tries to add you as a contact + 有陌生人希望连接你 + + + + Reject + Label for button which rejects a contact request when pressed + 拒绝 + + + + Rejects the incoming contact request + Description of what 'Reject' button does for accessibility tech like screen readers + + + + + Accept + Label for button which accepts a contact request when pressed + 接受 + + + + Accepts the incoming contact request + Description of what 'Accept' button does for accessibility tech like screen readers + + + + + ContactRequestFields + + + ID: + Label for the contact id text box in the 'add new contact' window + 帐号: + + + + Name: + Label for the contact nickname text box in the 'add new contact' window + 名字: + + + + Field for the contact's nickname + Description of textbox for setting a contact's nickname for accessibility tech like screen readers + + + + + Message: + Label for the contact greeting message text box in the 'add new contact' window + 消息: + + + + Field for the contact's greeting message + Description of textbox for setting a new contact's initial greeting message for accessibility tech like screen readers + + + + + GeneralPreferences + + + Use a single window for conversations + Text description of an option to use one single program window for the contact list and the chats + + + + Open links in default browser without prompting + Text description of an option to open URLs in default web browser when clicked + 无需提示,直接在默认浏览器中打开链接 + + + + Play audio notifications + Text description of an option to play audio notifications when contacts log in, log out, and send messages + 使用声音提醒 + + + + + Volume + Label for a slider used to adjust audio notification volume +---------- +Name of the slider used to adjust audio notification volume for accessibility tech like screen readers + 音量 + + + + + Language + Label for combobox where users can specify the UI language +---------- +Name of the combobox used to select UI langauge for accessibility tech like screen readers + 语言 + + + + Restart Ricochet to apply changes + 应用并重启 + + + + What language ricochet will use + Description of what the language combox is for for accessibility tech like screen readers + + + + + LanguagePreferences + + Select Language + 选择语言 + + + Restart Ricochet to apply changes + 应用并重启 + + + + LanguagesModel + + + System default + 系统默认 + + + + Main + + + Ricochet Error + Ricochet错误 + + + + MainToolBar + + + + Add Contact + Tooltip text for the button that launches the dialog box for adding a new contact +---------- +Name of the button for adding a new contact for accessibility tech like screen readers + 添加联系人 + + + + + Preferences + Tooltip text for the button that launches program preferences window +---------- +Name of the button for launching the preferences window for accessibility tech like screen readres + 选项 + + + + Click to add contacts + Tooltip that displays on first launch indicating how to add a new contact + 点击添加联系人 + + + + Shows the add contact dialogue + Description of the 'Add Contact' button for accessibility tech like screen readers + + + + + Shows the preferences dialogue + Description of the 'Preferences' button for accessibility tech like screen readers + + + + + MainWindow + + + Version Seperator + + + + + Remove %1 + 移除%1 + + + + Do you want to permanently remove %1? + 确定永久移除%1? + + + + Main Toolbar + Name of the main toolbar for accessibility tech like screen readers + + + + + Toolbar with connection status, add contact button, and preferences button + Description of the main toolbar for accessibility tech like screen readers + + + + + Contact pane + Name of the pane holding the user's contacts for accessibility tech like screen readers + + + + + MessageDelegate + + + %1 is offline + %1 nickname + %1离线中 + + + + File transfer file name + Description of the text displaying the filename of a file transfer, used by accessibility tech like screen readres + + + + + File transfer progress + Description of progress bar displaying the file transfer progress, used by accessibility tech like screen readers + + + + + File transfer status + Description of label displaying the current status of a file transfer, used by accessibility tech like screen readers + + + + + Download + Label for file transfer 'Download' button for accessibility tech like screen readers + + + + + Download file + Description of what the file transfer 'Download' button does for accessibility tech like screen readers + + + + + Cancel or reject + Label for file transfer 'Cancel' button for accessibility tech like screen readers + + + + + Cancels or rejects a file transfer + Description of what the file transfer 'Cancel' button does for accessibility tech like screen readers + + + + Copy ID + Text for context menu command to copy a ricochet contact id to clipboard + 复制帐号 + + + Copy Link + Text for context menu command to copy a url to the clipboard + 复制链接 + + + Open with Browser + Text for context menu command to open a url in a web browser + 用浏览器打开 + + + Add as Contact + 添加为联系人 + + + + Copy Message + Text for context menu command to copy an entire message to clipboard + 复制消息 + + + + Copy Selection + Text for context menu command to copy selected text to clipboard + 复制选择内容 + + + + MessageDialogWrapper + + + Remove %1 + A command to remove a contact from the contact list, %1 is the contact's nickname + 移除%1 + + + + Do you want to permanently remove %1? + Confirmation of contact removal, %1 is the contact's nickname + 确定永久移除%1? + + + + This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. + Message describing what will happen when a contact is removed from you contact list + + + + + NetworkSetupWizard + + + This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. + A label with directions for when to use the 'Connect' button + 这台电脑的网络连接没有任何障碍,我希望直接连到Tor网络。 + + + + Connect + Label for button to connect to the Tor network + 连接 + + + + This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. + A label with directions for when to use the 'Configure' button + 这台电脑的网络连接是经过审查、过滤或代理的,我希望配置网络设置。 + + + + Configure + Label for button to configure the Tor daemon beore connecting to the Tor network + 配置 + + + + OfflineStateItem + + + Configure + Button label + 配置 + + + + Details + Button label + 细节 + + + + Connection failed + Label displayed when connecting to the Tor network fails + 连接失败 + + + + Connecting… + Label displayed when in process of connecting, … is ellipsis + 连接中…… + + + + OpenBrowserDialog + + Don't ask again for links from %1 + 不再询问来自%1的链接 + + + Don't ask again for any links (not recommended!) + Checkbox option text for when user clicks on a link + 不再询问任何链接(不推荐) + + + Open Browser + Label on button to open link in a web browser + 打开浏览器 + + + Cancel + Label on cancel button + 取消 + + + + PreferencesDialog + + + Ricochet Preferences + Ricochet选项 + + + + Menu Tabs + Name of the tab bar for accessibility tech like screen readers + + + + + General + Title of the general settings tab + 通用 + + + Language + 语言 + + + + Contacts + Title of the contacts list tab + 联系人 + + + + Tor + Title of the tor tab, contains tor settings and logs + Tor + + + + About + Title of the about tab, contains license information and ricochet version + 关于 + + + + QCocoaMenuItem + + + Preference + 选项 + + + + StartupStatusPage + + + The Tor process was not started successfully. This is most likely an installation or system error. + Error status string displayed when tor daemon does not launch successfully + Tor 没有启动成功。可能需要重新安装。 + + + + Quit + Button title to quit/terminate the program + 退出 + + + + TorBootstrapStatus + + + Connecting to the Tor network… + Text status displayed when tor daemon is connecting, … is ellipsis + 连接到Tor网络中…… + + + + Connection status + Description of the connection status text, used by accessibility tech like screen readers + + + + + Connection progress + Description of the bootstrap progress bar, used by accessibility tech like screen readers + + + + + + Back + Label for button which moves us back to previous screen + 返回 + + + + Hide Details + Label for button which hides the tor logs + + + + + Show Details + Label for button which shows the tor logs + + + + + Hides the tor progress log + Description of hide details button, used by accessibility tech like screen readers + + + + + Shows the tor progress log + Description of show details button, used by accessibility tech like screen readers + + + + Hide details + 隐藏细节 + + + Show details + 显示细节 + + + + Done + Label for button which closes the tor connection window + 已完成 + + + + TorConfigurationPage + + + Does this computer need a proxy to access the internet? + 这台电脑需要通过代理来连接网络吗? + + + + Proxy type: + 代理类型: + + + + + None + None + + + + If you need a proxy to access the internet, select one from this list. + Description used by accessibility tech, such as screen readers + + + + + Address: + Label indicating the textbox to place a proxy IP or URL + 地址: + + + + IP address or hostname + Placeholder text of text box expecting an IP or URL for proxy + IP地址或主机名 + + + + Enter the IP address or hostname of the proxy you wish to connect to + Description of what to enter into the IP textbox, used by accessibility tech such as screen readers + + + + + Port: + Label indicating the textbox to place a proxy port + 端口: + + + + Port + Name of the port label, used by accessibility tech such as screen readers + + + + + Enter the port of the proxy you wish to connect to + Description of what to enter into the Port textbox, used by accessibility tech such as screen readers + + + + + Username: + Label indicating the textbox to place the proxy username + 用户名: + + + + + Optional + Textbox placeholder text indicating the field is not required + 可选 + + + + Username + Name of the username label, used by accessibility tech such as screen readers + + + + + If required, enter the username for the proxy you wish to connect to + Description to enter into the Username textbox, used by accessibility tech such as screen readers + + + + + Password: + Label indicating the textbox to place the proxy password + 密码: + + + + Password + Name of the password label, used by accessibility tech such as screen readers + + + + + If required, enter the password for the proxy you wish to connect to + Description to enter into the Password textbox, used by accessibility tech such as screen readers + + + + + Does this computer's Internet connection go through a firewall that only allows connections to certain ports? + Description for the purpose of the Allowed Ports textbox + + + + + Allowed ports: + Label indicating the textbox to place the allowed ports + 允许的端口: + + + + Example: 80,443 + Textbox showing an example entry for the firewall allowed ports entry + 例如:80,443 + + + + Allowed ports + Name of the allowed ports label, used by accessibility tech such as screen readers + + + + + If this computer's Internet connection is censored, you will need to obtain and use bridge relays. + + + + + + Enter one or more bridge relays (one per line): + + + + + Back + Button label for going back to previous screen + 返回 + + + + Connect + Button label for connecting to tor + 连接 + + + + TorLogDisplay + + + Tor log + Name of the text field containg the tor logs, used by accessibility tech such as screen readers + + + + + TorPreferences + + + Running: + Display label that beside it indicates whether tor is running + 运行中 + + + + Running + + + + + Whether tor is running + Description of the value of this label, used by acccessibility tech like screen readers + + + + + + Yes + + + + + + No + + + + External + 外部 + + + + Control connected: + Display label that beside it indicates whether ricochet is connected to tor's control port + 联系人已连接: + + + + Control connected + + + + + Whether tor control connected + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Circuits established: + 线路已经建立: + + + + Circuits established + + + + + Whether circuits established + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Hidden service: + 隐藏的服务: + + + + Hidden service + + + + + Online + 在线 + + + + Offline + 离线 + + + + Whether a hidden service is up or not + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Version: + 版本: + + + + Version + + + + + Error: <b>%1</b> + %1 is error message + 错误: <b>%1</b> + + + + Configure + 配置 + + + + TorStateWidget + + + + Connection failed + 连接失败 + + + + + Connecting… + … is ellipsis + 连接中…… + + + + Connecting… (%1%) + %1 is progress percentage, e.g. 100 + 连接中……(%1%) + + + + Online + 在线 + + + + Connected + 已连接 + + + + Tor status + + + + + shims::ConversationModel + + + Pending + + + + + Accepted + 已接受 + + + + Rejected + 已拒绝 + + + + Cancelled + + + + + Complete + + + + + Unkown Failure + + + + + Bad File Hash + + + + + Network Error + + + + + File System Error + + + + + Invalid + + + + + + Open File + + + + + + + + me + + + + + Save File + + + + diff --git a/src/ricochet-refresh/translation/ricochet_zh_HK.ts b/src/ricochet-refresh/translation/ricochet_zh_HK.ts new file mode 100644 index 00000000..48e1a191 --- /dev/null +++ b/src/ricochet-refresh/translation/ricochet_zh_HK.ts @@ -0,0 +1,1257 @@ + + + + + AboutPreferences + + Ricochet %1 + %1 version, e.g. 1.0.0 + Ricochet %1 + + + + Ricochet-Refresh %1 + %1 version, e.g. 1.0.0 + + + + + Ricochet Refresh version %1 + provides a readable interpretation of the Ricochet-Refresh version number for accessibility tech like screen readers + + + + + The license of Ricochet Refresh and its dependencies + + + + + License + + + + + About + 關於 + + + + About page, contains license and version information + summary of the window's contents for accessibility tech like screen readers + + + + + Current Ricochet-Refresh version + + + + + AddContactDialog + + + Share your Ricochet ID to allow connection requests + tells the user the purpose of their Ricochet ID, which is basically a username + 分享你的Ricochet ID 以允許連線的請求 + + + + Cancel + label for button which dismisses a dialog + 取消 + + + + Closes the contact add window + description for 'Cancel' button accessibility tech like screen readers + + + + + Add + button label to finish adding a contact/friend + 新增 + + + + Adds the contact to your contact list + description for 'Add' button for accessibility tech like screen readres + + + + + ChatPage + + + Message area + label for accessibility tech like screen readers + + + + + Write the message to be sent here. Press enter to send + description of the text area used to send messages for accessibility tech like screen readers + + + + + ContactActions + + + Warning + + + + + Could not successfully export conversation + + + + + Open Window + Context menu command to open the chat screen in a separate window + 開啟視窗 + + + + Details... + Context menu command to open a window showing the selected contact's details + 細節 + + + + Rename + Context menu command to rename the selected contact + 重新命名 + + + + Send File... + Context menu command to initiate a file transfer, opens a system file dialog + + + + + Export Conversation + Context menu command to initiate a chat log export, opens a system file dialog to export to + + + + + Remove + Context menu command to remove a contact from the contact list + 移除 + + + + Contact options + Description of the items in the context menu for accessibility tech like screen readers + + + + + ContactIDField + + + <b>%1</b> is already your contact + Error message showed when user attempts to add a contact already in their contact list + <b>%1</b> 已是你的聯絡人 + + + + This ID is invalid + Error message showed when the id doesn't comply with spec https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt + + + + + You can't add yourself as a contact + Error message showed when user attempts to add themselves as a contact in their contact list + 你不能新增自己為聯絡人 + + + + Enter an ID starting with <b>ricochet:</b> + Error message showed when the provided ricochet id is invalid + 輸入ID開始使用 <b>ricochet:</b> + + + + Copied to clipboard + Message displayed when text is copied to the user's clipboard + 複製到剪貼簿 + + + + Copy + Text displayed on a button used to copy somethign to the user's clipboard + 複製 + + + + Copies the ricochet id to the clipboard + Text description of ricochet id copy button for accessibility tech like screen readers + + + + + ContactList + + + Online + Section header in the contact list for users which are online + 上線 + + + + Offline + Section header in the contact list for users which are offline + 離線 + + + + Requests + Section header in the contact list for users requesting to be added to the user's contact list + 請求 + + + + Rejected + Section header in the contact list for users that have rejected the user's request to be added to their contact list + 拒絕 + + + + Status for the given contacts + Description of the section header for accessibility tech like screen readers + + + + Outdated + 過時 + + + + ContactPreferences + + Date added: + 新增之日期: + + + Last seen: + 上回瀏覧: + + + Request: + 請求: + + + Pending connection + 連線擱置中 + + + Delivered + 已傳送 + + + Accepted + 已接受 + + + Error + 錯誤 + + + Rejected + 拒絕 + + + %1 (Connected) + %1 status, e.g. "Accepted" + %1 (己連結) + + + Response: + 回應: + + + + Contact list + Description of the list of contacts for accessibility tech like screen readers + + + + + Contact ID for + Description of text box containing a contact's contact id for accessibility tech like screen readers + + + + + + Unknown user + A placeholder name for a contact whose name we do not know + + + + + Rename + Label for button which allows renaming of a contact + 重新命名 + + + + Renames this contact + Description of button which renames a contact for accessibility tech like screen readers + + + + + Remove + Label for button which removes a contact from the contact list + 移除 + + + + Removes this contact + Description of button which removes a user from the contact list for accessibility tech like screen readers + + + + + Preferences for contact + Description of the contents of the 'Contacts' window for accessibility tech like screen readers + + + + + Contact Preferences Window + Name of the contact preferences window for accessibility tech like screen readers + + + + + A list of all your contacts, with their ricochet IDs, and options such as renaming and removing + Description of what user can do in the contact preferences window for accessibility tech like screen readers + + + + + ContactRequestDialog + + + Someone new is asking to connect to you + Descriptive text that is displayed in a popup window when a user tries to add you as a contact + 某新人要求與你連線 + + + + Reject + Label for button which rejects a contact request when pressed + 拒絕 + + + + Rejects the incoming contact request + Description of what 'Reject' button does for accessibility tech like screen readers + + + + + Accept + Label for button which accepts a contact request when pressed + 接受 + + + + Accepts the incoming contact request + Description of what 'Accept' button does for accessibility tech like screen readers + + + + + ContactRequestFields + + + ID: + Label for the contact id text box in the 'add new contact' window + ID: + + + + Name: + Label for the contact nickname text box in the 'add new contact' window + 名稱: + + + + Field for the contact's nickname + Description of textbox for setting a contact's nickname for accessibility tech like screen readers + + + + + Message: + Label for the contact greeting message text box in the 'add new contact' window + 訊息: + + + + Field for the contact's greeting message + Description of textbox for setting a new contact's initial greeting message for accessibility tech like screen readers + + + + + GeneralPreferences + + + Use a single window for conversations + Text description of an option to use one single program window for the contact list and the chats + 使用單一視窗來對話 + + + Open links in default browser without prompting + Text description of an option to open URLs in default web browser when clicked + 在原瀏覧器中開啟連結而不要跳出新視窗 + + + + Play audio notifications + Text description of an option to play audio notifications when contacts log in, log out, and send messages + 開啟通知音訊 + + + + + Volume + Label for a slider used to adjust audio notification volume +---------- +Name of the slider used to adjust audio notification volume for accessibility tech like screen readers + 音量 + + + + + Language + Label for combobox where users can specify the UI language +---------- +Name of the combobox used to select UI langauge for accessibility tech like screen readers + 語言 + + + + Restart Ricochet to apply changes + 重啟Ricochet 以更新改變 + + + + What language ricochet will use + Description of what the language combox is for for accessibility tech like screen readers + + + + + LanguagePreferences + + Select Language + 選取語言 + + + Restart Ricochet to apply changes + 重啟Ricochet 以更新改變 + + + + LanguagesModel + + + System default + 系統預設 + + + + Main + + + Ricochet Error + Ricochet 出錯 + + + + MainToolBar + + + + Add Contact + Tooltip text for the button that launches the dialog box for adding a new contact +---------- +Name of the button for adding a new contact for accessibility tech like screen readers + 新增聯絡人 + + + + + Preferences + Tooltip text for the button that launches program preferences window +---------- +Name of the button for launching the preferences window for accessibility tech like screen readres + 偏好 + + + + Click to add contacts + Tooltip that displays on first launch indicating how to add a new contact + 點擊來新增聯絡人 + + + + Shows the add contact dialogue + Description of the 'Add Contact' button for accessibility tech like screen readers + + + + + Shows the preferences dialogue + Description of the 'Preferences' button for accessibility tech like screen readers + + + + + MainWindow + + + Version Seperator + + + + + Remove %1 + 移除%1 + + + + Do you want to permanently remove %1? + 你是否要永遠移除%1? + + + + Main Toolbar + Name of the main toolbar for accessibility tech like screen readers + + + + + Toolbar with connection status, add contact button, and preferences button + Description of the main toolbar for accessibility tech like screen readers + + + + + Contact pane + Name of the pane holding the user's contacts for accessibility tech like screen readers + + + + + MessageDelegate + + + %1 is offline + %1 nickname + %1 已離線 + + + + File transfer file name + Description of the text displaying the filename of a file transfer, used by accessibility tech like screen readres + + + + + File transfer progress + Description of progress bar displaying the file transfer progress, used by accessibility tech like screen readers + + + + + File transfer status + Description of label displaying the current status of a file transfer, used by accessibility tech like screen readers + + + + + Download + Label for file transfer 'Download' button for accessibility tech like screen readers + + + + + Download file + Description of what the file transfer 'Download' button does for accessibility tech like screen readers + + + + + Cancel or reject + Label for file transfer 'Cancel' button for accessibility tech like screen readers + + + + + Cancels or rejects a file transfer + Description of what the file transfer 'Cancel' button does for accessibility tech like screen readers + + + + Copy ID + Text for context menu command to copy a ricochet contact id to clipboard + 複製ID + + + Copy Link + Text for context menu command to copy a url to the clipboard + 複製連結 + + + Open with Browser + Text for context menu command to open a url in a web browser + 開啟瀏覧器 + + + Add as Contact + 新增聯絡人 + + + + Copy Message + Text for context menu command to copy an entire message to clipboard + 複製訊息 + + + + Copy Selection + Text for context menu command to copy selected text to clipboard + 複製選項 + + + + MessageDialogWrapper + + + Remove %1 + A command to remove a contact from the contact list, %1 is the contact's nickname + 移除%1 + + + + Do you want to permanently remove %1? + Confirmation of contact removal, %1 is the contact's nickname + 你是否要永遠移除%1? + + + + This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. + Message describing what will happen when a contact is removed from you contact list + 這名聯絡人不再傳訊給你,也將接到遭移除的通知。他們可以選擇重新送出連結請求 + + + + NetworkSetupWizard + + + This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. + A label with directions for when to use the 'Connect' button + 這台電腦的網路連結未有任何阻礙,可以直接連結上Tor網路 + + + + Connect + Label for button to connect to the Tor network + 連線 + + + + This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. + A label with directions for when to use the 'Configure' button + 如果這台電腦的網路連線遭到監控,我必須修改網路設定 + + + + Configure + Label for button to configure the Tor daemon beore connecting to the Tor network + 設定 + + + + OfflineStateItem + + + Configure + Button label + 設定 + + + + Details + Button label + 細節 + + + + Connection failed + Label displayed when connecting to the Tor network fails + 連線失敗 + + + + Connecting… + Label displayed when in process of connecting, … is ellipsis + 連線中 + + + + OpenBrowserDialog + + <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. + Label displayed when user clicks on a link + <b>警告!</b> 用你的瀏覧器直接開啟連結可能會對安全與匿名性造成威脅.<br><br>你可另以<a href='.'>複製到剪貼簿</a>取代 . + + + Don't ask again for links from %1 + 別再請求來自%1的連結 + + + Don't ask again for any links (not recommended!) + Checkbox option text for when user clicks on a link + 別再請求任何連結(不建議!) + + + Open Browser + Label on button to open link in a web browser + 開啟瀏覧器 + + + Cancel + Label on cancel button + 取消 + + + + PreferencesDialog + + + Ricochet Preferences + Ricochet 偏好 + + + + Menu Tabs + Name of the tab bar for accessibility tech like screen readers + + + + + General + Title of the general settings tab + 一般 + + + Language + 語言 + + + + Contacts + Title of the contacts list tab + 聯絡人 + + + + Tor + Title of the tor tab, contains tor settings and logs + Tor + + + + About + Title of the about tab, contains license information and ricochet version + 關於 + + + + QCocoaMenuItem + + + Preference + 偏好 + + + + StartupStatusPage + + + The Tor process was not started successfully. This is most likely an installation or system error. + Error status string displayed when tor daemon does not launch successfully + Tor連結過程並未成功啟動,這大部份是由於安裝或系統出錯 + + + + Quit + Button title to quit/terminate the program + 中止 + + + + TorBootstrapStatus + + + Connecting to the Tor network… + Text status displayed when tor daemon is connecting, … is ellipsis + 連線到Tor網路 + + + + Connection status + Description of the connection status text, used by accessibility tech like screen readers + + + + + Connection progress + Description of the bootstrap progress bar, used by accessibility tech like screen readers + + + + + + Back + Label for button which moves us back to previous screen + 退後 + + + + Hide Details + Label for button which hides the tor logs + + + + + Show Details + Label for button which shows the tor logs + + + + + Hides the tor progress log + Description of hide details button, used by accessibility tech like screen readers + + + + + Shows the tor progress log + Description of show details button, used by accessibility tech like screen readers + + + + Hide details + 隱藏細節 + + + Show details + 顯示細節 + + + + Done + Label for button which closes the tor connection window + 完成 + + + + TorConfigurationPage + + + Does this computer need a proxy to access the internet? + 這台電腦是否需要透過代理主機來連網? + + + + Proxy type: + Proxy 類型: + + + + + None + + + + + If you need a proxy to access the internet, select one from this list. + Description used by accessibility tech, such as screen readers + + + + + Address: + Label indicating the textbox to place a proxy IP or URL + 地址: + + + + IP address or hostname + Placeholder text of text box expecting an IP or URL for proxy + I位置或主機名稱 + + + + Enter the IP address or hostname of the proxy you wish to connect to + Description of what to enter into the IP textbox, used by accessibility tech such as screen readers + + + + + Port: + Label indicating the textbox to place a proxy port + 埠部: + + + + Port + Name of the port label, used by accessibility tech such as screen readers + + + + + Enter the port of the proxy you wish to connect to + Description of what to enter into the Port textbox, used by accessibility tech such as screen readers + + + + + Username: + Label indicating the textbox to place the proxy username + 使用者名稱: + + + + + Optional + Textbox placeholder text indicating the field is not required + 選項 + + + + Username + Name of the username label, used by accessibility tech such as screen readers + + + + + If required, enter the username for the proxy you wish to connect to + Description to enter into the Username textbox, used by accessibility tech such as screen readers + + + + + Password: + Label indicating the textbox to place the proxy password + 密碼: + + + + Password + Name of the password label, used by accessibility tech such as screen readers + + + + + If required, enter the password for the proxy you wish to connect to + Description to enter into the Password textbox, used by accessibility tech such as screen readers + + + + + Does this computer's Internet connection go through a firewall that only allows connections to certain ports? + Description for the purpose of the Allowed Ports textbox + 這台電腦的網路連線是否透過防火牆,只能充許某些埠號來連網? + + + + Allowed ports: + Label indicating the textbox to place the allowed ports + 允許埠部: + + + + Example: 80,443 + Textbox showing an example entry for the firewall allowed ports entry + 示範: 80,443 + + + + Allowed ports + Name of the allowed ports label, used by accessibility tech such as screen readers + + + + + If this computer's Internet connection is censored, you will need to obtain and use bridge relays. + 如果這台電腦的網路連線遭到監控,你必須改用中續撟接服務 + + + + + Enter one or more bridge relays (one per line): + 輸入一個以上的中續橋接(一行一個): + + + + Back + Button label for going back to previous screen + 退後 + + + + Connect + Button label for connecting to tor + 連線 + + + + TorLogDisplay + + + Tor log + Name of the text field containg the tor logs, used by accessibility tech such as screen readers + + + + + TorPreferences + + + Running: + Display label that beside it indicates whether tor is running + 執行中: + + + + Running + + + + + Whether tor is running + Description of the value of this label, used by acccessibility tech like screen readers + + + + + + Yes + + + + + + No + + + + External + 外部 + + + + Control connected: + Display label that beside it indicates whether ricochet is connected to tor's control port + 連線控制: + + + + Control connected + + + + + Whether tor control connected + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Circuits established: + 已建立巡迴: + + + + Circuits established + + + + + Whether circuits established + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Hidden service: + 隱藏服務: + + + + Hidden service + + + + + Online + 上線 + + + + Offline + 離線 + + + + Whether a hidden service is up or not + Description of the value of this label, used by acccessibility tech like screen readers + + + + + Version: + 版本: + + + + Version + + + + + Error: <b>%1</b> + %1 is error message + <b>%1</b>出錯 + + + + Configure + 設定 + + + + TorStateWidget + + + + Connection failed + 連線失敗 + + + + + Connecting… + … is ellipsis + 連線中 + + + + Connecting… (%1%) + %1 is progress percentage, e.g. 100 + (%1%)連線中… + + + + Online + 上線 + + + + Connected + 已連結 + + + + Tor status + + + + + shims::ConversationModel + + + Pending + + + + + Accepted + 已接受 + + + + Rejected + 拒絕 + + + + Cancelled + + + + + Complete + + + + + Unkown Failure + + + + + Bad File Hash + + + + + Network Error + + + + + File System Error + + + + + Invalid + + + + + + Open File + + + + + + + + me + + + + + Save File + + + + diff --git a/src/ricochet-refresh/translation/update_translations.sh b/src/ricochet-refresh/translation/update_translations.sh new file mode 100755 index 00000000..085a8068 --- /dev/null +++ b/src/ricochet-refresh/translation/update_translations.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +# see https://doc.qt.io/qt-5/qtquick-internationalization.html for info +# on annotating strings for translation + +# this command parses the source and populates our ts files with new +# translation strings +lupdate-pro ../tego_ui.pro -ts *.ts diff --git a/src/ricochet-refresh/ui/AboutPreferences.qml b/src/ricochet-refresh/ui/AboutPreferences.qml new file mode 100644 index 00000000..0bf92c0d --- /dev/null +++ b/src/ricochet-refresh/ui/AboutPreferences.qml @@ -0,0 +1,41 @@ +import QtQuick 2.15 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.0 + +ColumnLayout { + anchors { + fill: parent + margins: 8 + } + + Label { + Layout.fillWidth: true + //: %1 version, e.g. 1.0.0 + text: qsTr("Ricochet-Refresh %1").arg(uiMain.version) + horizontalAlignment: Qt.AlignHCenter + + Accessible.description: qsTr("Current Ricochet-Refresh version") + Accessible.role: Accessible.StaticText + //: provides a readable interpretation of the Ricochet-Refresh version number for accessibility tech like screen readers + Accessible.name: qsTr("Ricochet Refresh version %1").arg(uiMain.accessibleVersion) + } + + TextArea { + Layout.fillWidth: true + Layout.fillHeight: true + + readOnly: true + text: uiMain.aboutText + textFormat: TextEdit.PlainText + wrapMode: TextEdit.Wrap + + Accessible.description: qsTr("The license of Ricochet Refresh and its dependencies") + Accessible.name: qsTr("License") + } + + Accessible.role: Accessible.Window + Accessible.name: qsTr("About") + //: summary of the window's contents for accessibility tech like screen readers + Accessible.description: qsTr("About page, contains license and version information") +} + diff --git a/src/ui/qml/AddContactDialog.qml b/src/ricochet-refresh/ui/AddContactDialog.qml similarity index 74% rename from src/ui/qml/AddContactDialog.qml rename to src/ricochet-refresh/ui/AddContactDialog.qml index 4c9a982b..077c5411 100644 --- a/src/ui/qml/AddContactDialog.qml +++ b/src/ricochet-refresh/ui/AddContactDialog.qml @@ -4,7 +4,7 @@ import QtQuick.Layouts 1.0 ApplicationWindow { id: addContactWindow - width: 400 + width: 640 height: 300 minimumWidth: width maximumWidth: width @@ -48,7 +48,10 @@ ApplicationWindow { Layout.fillWidth: true horizontalAlignment: Qt.AlignHCenter wrapMode: Text.Wrap + //: tells the user the purpose of their Ricochet ID, which is basically a username text: qsTr("Share your Ricochet ID to allow connection requests") + Accessible.role: Accessible.StaticText + Accessible.name: text } ContactIDField { @@ -56,7 +59,7 @@ ApplicationWindow { Layout.fillWidth: true readOnly: true text: userIdentity.contactID - horizontalAlignment: Qt.AlignHCenter + horizontalAlignment: Qt.AlignLeft } Item { height: 1 } @@ -104,15 +107,28 @@ ApplicationWindow { } Button { + //: label for button which dismisses a dialog text: qsTr("Cancel") onClicked: addContactWindow.close() + Accessible.role: Accessible.Button + Accessible.name: text + //: description for 'Cancel' button accessibility tech like screen readers + Accessible.description: qsTr("Closes the contact add window") + Accessible.onPressAction: addContactWindow.close() } Button { + //: button label to finish adding a contact/friend text: qsTr("Add") isDefault: true enabled: fields.hasValidRequest onClicked: addContactWindow.accept() + + Accessible.role: Accessible.Button + Accessible.name: text + //: description for 'Add' button for accessibility tech like screen readres + Accessible.description: qsTr("Adds the contact to your contact list") + Accessible.onPressAction: addContactWindow.close() } } diff --git a/src/ui/qml/AudioNotifications.qml b/src/ricochet-refresh/ui/AudioNotifications.qml similarity index 100% rename from src/ui/qml/AudioNotifications.qml rename to src/ricochet-refresh/ui/AudioNotifications.qml diff --git a/src/ui/qml/Bubble.qml b/src/ricochet-refresh/ui/Bubble.qml similarity index 94% rename from src/ui/qml/Bubble.qml rename to src/ricochet-refresh/ui/Bubble.qml index 2e3e18d0..5d9d760d 100644 --- a/src/ui/qml/Bubble.qml +++ b/src/ricochet-refresh/ui/Bubble.qml @@ -55,6 +55,9 @@ Rectangle { textFormat: Text.PlainText x: 6 y: 6 + + Accessible.name: text + Accessible.role: Accessible.StaticText } } diff --git a/src/ui/qml/ChatMessageArea.qml b/src/ricochet-refresh/ui/ChatMessageArea.qml similarity index 100% rename from src/ui/qml/ChatMessageArea.qml rename to src/ricochet-refresh/ui/ChatMessageArea.qml diff --git a/src/ui/qml/ChatPage.qml b/src/ricochet-refresh/ui/ChatPage.qml similarity index 86% rename from src/ui/qml/ChatPage.qml rename to src/ricochet-refresh/ui/ChatPage.qml index e24a3a96..31c6abc5 100644 --- a/src/ui/qml/ChatPage.qml +++ b/src/ricochet-refresh/ui/ChatPage.qml @@ -6,7 +6,7 @@ import im.ricochet 1.0 FocusScope { id: chatPage - property var contact + property ContactUser contact property TextArea textField: textInput property var conversationModel: (contact !== null) ? contact.conversation : null @@ -24,7 +24,9 @@ FocusScope { Connections { target: conversationModel - onUnreadCountChanged: if (active) conversationModel.resetUnreadCount() + function onUnreadCountChanged(user, unreadCount) { + if (active) conversationModel.resetUnreadCount() + } } RowLayout { @@ -40,11 +42,12 @@ FocusScope { spacing: 8 PresenceIcon { - status: contact.status + // 1 = Status::Offline + status: contact != null ? contact.status : 1 } Label { - text: contact.nickname + text: contact != null ? contact.nickname : "" textFormat: Text.PlainText font.pointSize: styleHelper.pointSize } @@ -156,6 +159,12 @@ FocusScope { if (textInput.length > 2000) textInput.remove(2000, textInput.length) } + + Accessible.role: Accessible.EditableText + //: label for accessibility tech like screen readers + Accessible.name: qsTr("Message area") // todo: translation + //: description of the text area used to send messages for accessibility tech like screen readers + Accessible.description: qsTr("Write the message to be sent here. Press enter to send") } } } diff --git a/src/ui/qml/ChatWindow.qml b/src/ricochet-refresh/ui/ChatWindow.qml similarity index 90% rename from src/ui/qml/ChatWindow.qml rename to src/ricochet-refresh/ui/ChatWindow.qml index ac793286..40ba8734 100644 --- a/src/ui/qml/ChatWindow.qml +++ b/src/ricochet-refresh/ui/ChatWindow.qml @@ -34,12 +34,6 @@ ApplicationWindow { } } - Timer { - id: retakeFocus - onTriggered: chatPage.forceActiveFocus() - interval: 1 - } - ChatPage { id: chatPage anchors.fill: parent diff --git a/src/ricochet-refresh/ui/ContactActions.qml b/src/ricochet-refresh/ui/ContactActions.qml new file mode 100644 index 00000000..3e01ee31 --- /dev/null +++ b/src/ricochet-refresh/ui/ContactActions.qml @@ -0,0 +1,113 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0 +import QtQuick.Dialogs 1.1 +import "ContactWindow.js" as ContactWindow + +Item { + id: contactMenu + + property QtObject contact + + function openWindow() { + var window = ContactWindow.getWindow(contact) + window.raise() + window.requestActivate() + } + + function removeContact() { + removeContactDialog.active = true + if (removeContactDialog.item !== null) { + removeContactDialog.item.open() + } else if (uiMain.showRemoveContactDialog(contact)) { + contact.deleteContact() + } + } + + function openContextMenu() { + contextMenu.popup() + } + + function openPreferences() { + root.openPreferences("ContactPreferences.qml", { 'selectedContact': contact }) + } + + function sendFile() { + contact.sendFile(); + } + + function canExportConversation() { + return contact.conversation.conversationEventCount > 0; + } + + function exportConversation() { + if (contact.exportConversation() != true) { + exportConversationFailedDialog.visible = true; + } + } + + signal renameTriggered + + MessageDialog { + id: exportConversationFailedDialog + + title: qsTr("Warning") + icon: StandardIcon.Warning + text: qsTr("Could not successfully export conversation") + standardButtons: StandardButton.Ok + + visible: false + + onAccepted: visible = false; + } + + Menu { + id: contextMenu + + /* QT automatically sets Accessible.text to MenuItem.text */ + MenuItem { + //: Context menu command to open the chat screen in a separate window + text: qsTr("Open Window") + onTriggered: openWindow() + } + MenuItem { + //: Context menu command to open a window showing the selected contact's details + text: qsTr("Details...") + onTriggered: openPreferences() + } + MenuItem { + //: Context menu command to rename the selected contact + text: qsTr("Rename") + onTriggered: renameTriggered() + } + MenuItem { + //: Context menu command to initiate a file transfer, opens a system file dialog + text: qsTr("Send File...") + onTriggered: sendFile() + } + MenuItem { + //: Context menu command to initiate a chat log export, opens a system file dialog to export to + enabled: canExportConversation() // only enable if we have anything to export + text: qsTr("Export Conversation...") + onTriggered: exportConversation() + } + MenuSeparator { } + MenuItem { + //: Context menu command to remove a contact from the contact list + text: qsTr("Remove") + onTriggered: removeContact() + } + } + + Accessible.role: Accessible.List + //: Description of the items in the context menu for accessibility tech like screen readers + Accessible.name: qsTr("Contact options") + + Loader { + id: removeContactDialog + source: "MessageDialogWrapper.qml" + active: false + + Accessible.role: Accessible.Window + } +} + diff --git a/src/ui/qml/ContactIDField.qml b/src/ricochet-refresh/ui/ContactIDField.qml similarity index 62% rename from src/ui/qml/ContactIDField.qml rename to src/ricochet-refresh/ui/ContactIDField.qml index 128111e4..4e286069 100644 --- a/src/ui/qml/ContactIDField.qml +++ b/src/ricochet-refresh/ui/ContactIDField.qml @@ -29,20 +29,35 @@ FocusScope { placeholderText: "ricochet:" focus: true - onTextChanged: errorBubble.clear() - ContactIDValidator { id: idValidator - notContactOfIdentity: userIdentity onFailed: { var contact if ((contact = matchingContact(field.text))) + { + //: Error message showed when user attempts to add a contact already in their contact list errorBubble.show(qsTr("%1 is already your contact").arg(Utils.htmlEscaped(contact.nickname))) + } + else if (!isValidID(field.text)) + { + //: Error message showed when the id doesn't comply with spec https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt + errorBubble.show(qsTr("This ID is invalid")); + } else if (matchesIdentity(field.text)) + { + //: Error message showed when user attempts to add themselves as a contact in their contact list errorBubble.show(qsTr("You can't add yourself as a contact")) + } else + { + //: Error message showed when the provided ricochet id is invalid errorBubble.show(qsTr("Enter an ID starting with ricochet:")) + } + } + + onSuccess: { + errorBubble.clear() } } @@ -63,8 +78,8 @@ FocusScope { } function copyLoudly() { - // The LinkedText helper also copies to the X11 selection clipboard - LinkedText.copyToClipboard(field.text) + // The Clipboard helper also copies to the X11 selection clipboard + Clipboard.copyText(field.text) copyBubble.displayed = true bubbleResetTimer.start() } @@ -78,8 +93,11 @@ FocusScope { Bubble { id: copyBubble target: field + //: Message displayed when text is copied to the user's clipboard text: qsTr("Copied to clipboard") displayed: false + Accessible.role: Accessible.StaticText + Accessible.name: text } Timer { @@ -90,9 +108,16 @@ FocusScope { } Button { + //: Text displayed on a button used to copy somethign to the user's clipboard text: qsTr("Copy") visible: contactId.showCopyButton onClicked: field.copyLoudly() + + Accessible.role: Accessible.Button + Accessible.name: text + //: Text description of ricochet id copy button for accessibility tech like screen readers + Accessible.description: qsTr("Copies the ricochet id to the clipboard") + Accessible.onPressAction: field.copyLoudly() } } } diff --git a/src/ui/qml/ContactList.qml b/src/ricochet-refresh/ui/ContactList.qml similarity index 80% rename from src/ui/qml/ContactList.qml rename to src/ricochet-refresh/ui/ContactList.qml index aa12e5c1..ae743b96 100644 --- a/src/ui/qml/ContactList.qml +++ b/src/ricochet-refresh/ui/ContactList.qml @@ -14,7 +14,6 @@ ScrollView { }, ContactsModel { id: contactsModel - identity: userIdentity } ] @@ -73,13 +72,21 @@ ScrollView { // should correctly be capitalized. We go lowercase only because it looks // nicer when using SmallCaps, and that's a display detail. switch (parseInt(section)) { + //: Section header in the contact list for users which are online case ContactUser.Online: return qsTr("Online").toLowerCase() + //: Section header in the contact list for users which are offline case ContactUser.Offline: return qsTr("Offline").toLowerCase() + //: Section header in the contact list for users requesting to be added to the user's contact list case ContactUser.RequestPending: return qsTr("Requests").toLowerCase() + //: Section header in the contact list for users that have rejected the user's request to be added to their contact list case ContactUser.RequestRejected: return qsTr("Rejected").toLowerCase() - case ContactUser.Outdated: return qsTr("Outdated").toLowerCase() } } + + Accessible.role: Accessible.StaticText + Accessible.name: text + //: Description of the section header for accessibility tech like screen readers + Accessible.description: qsTr("Status for the given contacts") } Rectangle { diff --git a/src/ui/qml/ContactListDelegate.qml b/src/ricochet-refresh/ui/ContactListDelegate.qml similarity index 98% rename from src/ui/qml/ContactListDelegate.qml rename to src/ricochet-refresh/ui/ContactListDelegate.qml index 15f7c274..231736fc 100644 --- a/src/ui/qml/ContactListDelegate.qml +++ b/src/ricochet-refresh/ui/ContactListDelegate.qml @@ -5,7 +5,7 @@ import im.ricochet 1.0 Rectangle { id: delegate color: highlighted ? "#c4e7ff" : "white" - width: parent.width + width: parent != null ? parent.width : 0 height: nameLabel.height + 8 property bool highlighted: ListView.isCurrentItem diff --git a/src/ui/qml/ContactPreferences.qml b/src/ricochet-refresh/ui/ContactPreferences.qml similarity index 57% rename from src/ui/qml/ContactPreferences.qml rename to src/ricochet-refresh/ui/ContactPreferences.qml index 1c09ee0e..12dd5b61 100644 --- a/src/ui/qml/ContactPreferences.qml +++ b/src/ricochet-refresh/ui/ContactPreferences.qml @@ -18,6 +18,10 @@ Item { Layout.minimumWidth: 150 Layout.fillHeight: true frameVisible: true + + Accessible.role: Accessible.List + //: Description of the list of contacts for accessibility tech like screen readers + Accessible.name: qsTr("Contact list") } data: [ @@ -89,63 +93,15 @@ Item { Layout.minimumWidth: 100 readOnly: true text: visible ? contactInfo.contact.contactID : "" - } - - GridLayout { - Layout.fillWidth: true - columns: 2 - - Label { text: qsTr("Date added:"); Layout.alignment: Qt.AlignRight } - Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: visible ? Qt.formatDate(contactInfo.contact.settings.read("whenCreated"), Qt.DefaultLocaleLongDate) : "" - textFormat: Text.PlainText - } - - Label { text: qsTr("Last seen:"); visible: lastSeen.visible; Layout.alignment: Qt.AlignRight } - Label { - id: lastSeen - Layout.fillWidth: true - elide: Text.ElideRight - visible: contactInfo.request === null - text: visible ? Qt.formatDate(contactInfo.contact.settings.read("lastConnected"), Qt.DefaultLocaleLongDate) : "" - textFormat: Text.PlainText - } - Label { text: qsTr("Request:"); visible: requestStatus.visible; Layout.alignment: Qt.AlignRight } - Label { - id: requestStatus - visible: contactInfo.request !== null - textFormat: Text.PlainText - text: { - var re = "" - if (contactInfo.request === null) - return re - switch (contactInfo.request.status) { - case OutgoingContactRequest.Pending: re = qsTr("Pending connection"); break - case OutgoingContactRequest.Acknowledged: re = qsTr("Delivered"); break - case OutgoingContactRequest.Accepted: re = qsTr("Accepted"); break - case OutgoingContactRequest.Error: re = qsTr("Error"); break - case OutgoingContactRequest.Rejected: re = qsTr("Rejected"); break - } - if (contactInfo.request.isConnected) { - //: %1 status, e.g. "Accepted" - re = qsTr("%1 (Connected)").arg(re) - } - return re - } - } - - Label { text: qsTr("Response:"); visible: rejectMessage.visible; Layout.alignment: Qt.AlignRight } - Label { - id: rejectMessage - Layout.fillWidth: true - elide: Text.ElideRight - text: visible ? contactInfo.request.rejectMessage : "" - textFormat: Text.PlainText - visible: (contactInfo.request !== null) && (contactInfo.request.rejectMessage !== "") - } + Accessible.role: Accessible.StaticText + //: Description of text box containing a contact's contact id for accessibility tech like screen readers + Accessible.name: qsTr("Contact ID for ") + + visible ? + nickname.text : + //: A placeholder name for a contact whose name we do not know + qsTr("Unknown user") + Accessible.description: text } Item { height: 1; width: 1 } @@ -160,15 +116,25 @@ Item { Layout.fillWidth: true Button { + //: Label for button which allows renaming of a contact text: qsTr("Rename") onClicked: nickname.renameMode = !nickname.renameMode + Accessible.role: Accessible.Button + Accessible.name: text + //: Description of button which renames a contact for accessibility tech like screen readers + Accessible.description: qsTr("Renames this contact") } Item { Layout.fillWidth: true; height: 1 } Button { + //: Label for button which removes a contact from the contact list text: qsTr("Remove") onClicked: contactActions.removeContact() + Accessible.role: Accessible.Button + Accessible.name: text + //: Description of button which removes a user from the contact list for accessibility tech like screen readers + Accessible.description: qsTr("Removes this contact") // todo: translation } } @@ -176,6 +142,19 @@ Item { Layout.fillHeight: true width: 1 } + + Accessible.role: Accessible.Window + //: Description of the contents of the 'Contacts' window for accessibility tech like screen readers + Accessible.name: qsTr("Preferences for contact ") + + visible ? + nickname.text : + //: A placeholder name for a contact whose name we do not know + qsTr("Unknown user") } } + Accessible.role: Accessible.Window + //: Name of the contact preferences window for accessibility tech like screen readers + Accessible.name: qsTr("Contact Preferences Window") + //: Description of what user can do in the contact preferences window for accessibility tech like screen readers + Accessible.description: qsTr("A list of all your contacts, with their ricochet IDs, and options such as renaming and removing") } diff --git a/src/ui/qml/ContactRequestDialog.qml b/src/ricochet-refresh/ui/ContactRequestDialog.qml similarity index 72% rename from src/ui/qml/ContactRequestDialog.qml rename to src/ricochet-refresh/ui/ContactRequestDialog.qml index 22b7d214..3897185e 100644 --- a/src/ui/qml/ContactRequestDialog.qml +++ b/src/ricochet-refresh/ui/ContactRequestDialog.qml @@ -4,7 +4,7 @@ import QtQuick.Layouts 1.0 ApplicationWindow { id: contactRequestDialog - width: 350 + width: 640 height: 200 minimumWidth: width maximumWidth: width @@ -18,7 +18,7 @@ ApplicationWindow { onVisibleChanged: if (!visible) closed() property QtObject request - property bool hasValidContact: request.hostname != "" && fields.name.text.length + property bool hasValidContact: request != null && request.hostname != "" && fields.name.text.length function close() { visible = false @@ -52,7 +52,10 @@ ApplicationWindow { Layout.fillWidth: true horizontalAlignment: Qt.AlignHCenter wrapMode: Text.Wrap + //: Descriptive text that is displayed in a popup window when a user tries to add you as a contact text: qsTr("Someone new is asking to connect to you") + Accessible.role: Accessible.PopupMenu + Accessible.name: text } Item { height: 1 } @@ -99,14 +102,24 @@ ApplicationWindow { } Button { + //: Label for button which rejects a contact request when pressed text: qsTr("Reject") onClicked: contactRequestDialog.reject() + Accessible.role: Accessible.Button + Accessible.name: text + //: Description of what 'Reject' button does for accessibility tech like screen readers + Accessible.description: qsTr("Rejects the incoming contact request") } Button { + //: Label for button which accepts a contact request when pressed text: qsTr("Accept") enabled: hasValidContact onClicked: contactRequestDialog.accept() + Accessible.role: Accessible.Button + Accessible.name: text + //: Description of what 'Accept' button does for accessibility tech like screen readers + Accessible.description: qsTr("Accepts the incoming contact request") } } diff --git a/src/ui/qml/ContactRequestFields.qml b/src/ricochet-refresh/ui/ContactRequestFields.qml similarity index 53% rename from src/ui/qml/ContactRequestFields.qml rename to src/ricochet-refresh/ui/ContactRequestFields.qml index 6cc75517..70382b68 100644 --- a/src/ui/qml/ContactRequestFields.qml +++ b/src/ricochet-refresh/ui/ContactRequestFields.qml @@ -14,8 +14,11 @@ GridLayout { property bool hasValidRequest: contactIdField.acceptableInput && nameField.text.length Label { + //: Label for the contact id text box in the 'add new contact' window text: qsTr("ID:") Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + Accessible.role: Accessible.StaticText + Accessible.name: text } ContactIDField { @@ -26,19 +29,30 @@ GridLayout { } Label { + //: Label for the contact nickname text box in the 'add new contact' window text: qsTr("Name:") Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + Accessible.role: Accessible.StaticText + Accessible.name: text } TextField { id: nameField Layout.fillWidth: true readOnly: contactFields.readOnly + + Accessible.role: Accessible.Dialog + Accessible.name: text + //: Description of textbox for setting a contact's nickname for accessibility tech like screen readers + Accessible.description: qsTr("Field for the contact's nickname") } Label { + //: Label for the contact greeting message text box in the 'add new contact' window text: qsTr("Message:") Layout.alignment: Qt.AlignTop | Qt.AlignRight + Accessible.role: Accessible.StaticText + Accessible.name: text } TextArea { @@ -47,5 +61,9 @@ GridLayout { Layout.fillHeight: true textFormat: TextEdit.PlainText readOnly: contactFields.readOnly + Accessible.role: Accessible.Dialog + Accessible.name: text + //: Description of textbox for setting a new contact's initial greeting message for accessibility tech like screen readers + Accessible.description: qsTr("Field for the contact's greeting message") } } diff --git a/src/ui/qml/ContactWindow.js b/src/ricochet-refresh/ui/ContactWindow.js similarity index 100% rename from src/ui/qml/ContactWindow.js rename to src/ricochet-refresh/ui/ContactWindow.js diff --git a/src/ui/qml/GeneralPreferences.qml b/src/ricochet-refresh/ui/GeneralPreferences.qml similarity index 54% rename from src/ui/qml/GeneralPreferences.qml rename to src/ricochet-refresh/ui/GeneralPreferences.qml index 845c7fd8..f91b1ad3 100644 --- a/src/ui/qml/GeneralPreferences.qml +++ b/src/ricochet-refresh/ui/GeneralPreferences.qml @@ -10,32 +10,43 @@ ColumnLayout { } CheckBox { + //: Text description of an option to use one single program window for the contact list and the chats text: qsTr("Use a single window for conversations") checked: uiSettings.data.combinedChatWindow || false onCheckedChanged: { uiSettings.write("combinedChatWindow", checked) } - } - CheckBox { - text: qsTr("Open links in default browser without prompting") - checked: uiSettings.data.alwaysOpenBrowser || false - onCheckedChanged: { - uiSettings.write("alwaysOpenBrowser", checked) + Accessible.role: Accessible.CheckBox + Accessible.name: text + Accessible.onPressAction: { + uiSettings.write("combinedChatWindow", checked) } } CheckBox { + //: Text description of an option to play audio notifications when contacts log in, log out, and send messages text: qsTr("Play audio notifications") checked: uiSettings.data.playAudioNotification || false onCheckedChanged: { uiSettings.write("playAudioNotification", checked) } + + Accessible.role: Accessible.CheckBox + Accessible.name: text + Accessible.onPressAction: { + uiSettings.write("playAudioNotification", checked) + } } RowLayout { Item { width: 16 } - Label { text: qsTr("Volume") } + Label { + //: Label for a slider used to adjust audio notification volume + text: qsTr("Volume") + Accessible.role: Accessible.StaticText + Accessible.name: text + } Slider { maximumValue: 1.0 @@ -45,12 +56,27 @@ ColumnLayout { onValueChanged: { uiSettings.write("notificationVolume", value) } + + Accessible.role: Accessible.Slider + //: Name of the slider used to adjust audio notification volume for accessibility tech like screen readers + Accessible.name: qsTr("Volume") + Accessible.onIncreaseAction: { + value += 0.125 // 8 volume settings + } + Accessible.onDecreaseAction: { + value -= 0.125 + } } } RowLayout { z: 2 - Label { text: qsTr("Language") } + Label { + //: Label for combobox where users can specify the UI language + text: qsTr("Language") + Accessible.role: Accessible.StaticText + Accessible.name: text + } ComboBox { id: languageBox @@ -83,10 +109,14 @@ ColumnLayout { onTriggered: restartBubble.displayed = false } } + Accessible.role: Accessible.ComboBox + //: Name of the combobox used to select UI langauge for accessibility tech like screen readers + Accessible.name: qsTr("Language") + //: Description of what the language combox is for for accessibility tech like screen readers + Accessible.description: qsTr("What language ricochet will use") } } - Item { Layout.fillHeight: true Layout.fillWidth: true diff --git a/src/ui/qml/MainToolBar.qml b/src/ricochet-refresh/ui/MainToolBar.qml similarity index 66% rename from src/ui/qml/MainToolBar.qml rename to src/ricochet-refresh/ui/MainToolBar.qml index a384a1a2..57be0f13 100644 --- a/src/ui/qml/MainToolBar.qml +++ b/src/ricochet-refresh/ui/MainToolBar.qml @@ -16,6 +16,7 @@ ToolBar { data: [ Action { id: addContactAction + //: Tooltip text for the button that launches the dialog box for adding a new contact text: qsTr("Add Contact") onTriggered: { var object = createDialog("AddContactDialog.qml", { }, window) @@ -25,6 +26,7 @@ ToolBar { Action { id: preferencesAction + //: Tooltip text for the button that launches program preferences window text: qsTr("Preferences") onTriggered: root.openPreferences() } @@ -51,7 +53,7 @@ ToolBar { width: parent.width TorStateWidget { - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter } Item { @@ -72,9 +74,16 @@ ToolBar { sourceComponent: Bubble { target: addContactButton maximumWidth: toolBarLayout.width + //: Tooltip that displays on first launch indicating how to add a new contact text: qsTr("Click to add contacts") } } + + Accessible.role: Accessible.Button + //: Name of the button for adding a new contact for accessibility tech like screen readers + Accessible.name: qsTr("Add Contact") + //: Description of the 'Add Contact' button for accessibility tech like screen readers + Accessible.description: qsTr("Shows the add contact dialogue") } ToolButton { @@ -82,6 +91,12 @@ ToolBar { implicitHeight: 24 style: iconButtonStyle text: "\ue803" // iconFont gear + + Accessible.role: Accessible.Button + //: Name of the button for launching the preferences window for accessibility tech like screen readres + Accessible.name: qsTr("Preferences") + //: Description of the 'Preferences' button for accessibility tech like screen readers + Accessible.description: qsTr("Shows the preferences dialogue") // todo: translation } } } diff --git a/src/ui/qml/MainWindow.qml b/src/ricochet-refresh/ui/MainWindow.qml similarity index 75% rename from src/ui/qml/MainWindow.qml rename to src/ricochet-refresh/ui/MainWindow.qml index b3189959..017c6595 100644 --- a/src/ui/qml/MainWindow.qml +++ b/src/ricochet-refresh/ui/MainWindow.qml @@ -10,14 +10,12 @@ ApplicationWindow { title: "Ricochet" visibility: Window.AutomaticVisibility - width: 250 + width: 650 height: 400 minimumHeight: 400 minimumWidth: uiSettings.data.combinedChatWindow ? 650 : 250 - maximumWidth: uiSettings.data.combinedChatWindow ? (1 << 24) - 1 : 250 onMinimumWidthChanged: width = Math.max(width, minimumWidth) - onMaximumWidthChanged: width = Math.min(width, maximumWidth) // OS X Menu Loader { @@ -35,7 +33,7 @@ ApplicationWindow { Connections { target: userIdentity.contacts - onUnreadCountChanged: { + function onUnreadCountChanged(user, unreadCount) { if (unreadCount > 0) { if (audioNotifications !== null) audioNotifications.message.play() @@ -46,7 +44,7 @@ ApplicationWindow { w.alert(Qt.platform.os == "osx" ? 1000 : 0) } } - onContactStatusChanged: { + function onContactStatusChanged(user, status) { if (status === ContactUser.Online && audioNotifications !== null) { audioNotifications.contactOnline.play() } @@ -66,6 +64,12 @@ ApplicationWindow { id: toolBar // Needed to allow bubble to appear over contact list z: 3 + + Accessible.role: Accessible.ToolBar + //: Name of the main toolbar for accessibility tech like screen readers + Accessible.name: qsTr("Main Toolbar") + //: Description of the main toolbar for accessibility tech like screen readers + Accessible.description: qsTr("Toolbar with connection status, add contact button, and preferences button") } Item { @@ -77,18 +81,22 @@ ApplicationWindow { anchors.fill: parent opacity: offlineLoader.item !== null ? (1 - offlineLoader.item.opacity) : 1 - onContactActivated: { + function onContactActivated(contact, actions) { if (contact.status === ContactUser.RequestPending || contact.status === ContactUser.RequestRejected) { actions.openPreferences() } else if (!uiSettings.data.combinedChatWindow) { actions.openWindow() } } + + Accessible.role: Accessible.Pane + //: Name of the pane holding the user's contacts for accessibility tech like screen readers + Accessible.name: qsTr("Contact pane") } Loader { id: offlineLoader - active: torControl.torStatus !== TorControl.TorReady || (item !== null && item.visible) + active: torControl.torStatus !== TorControl.TorReady anchors.fill: parent source: Qt.resolvedUrl("OfflineStateItem.qml") } @@ -111,7 +119,15 @@ ApplicationWindow { property QtObject currentContact: (visible && width > 0) ? contactList.selectedContact : null onCurrentContactChanged: { if (currentContact !== null) { - show(currentContact.uniqueID, Qt.resolvedUrl("ChatPage.qml"), + + // remove chat page for user when they are deleted + if(typeof currentContact.contactDeletedCallbackAdded === 'undefined') { + currentContact.contactDeleted.connect(function(user) { + remove(user.contactID); + }); + currentContact.contactDeletedCallbackAdded = true; + } + show(currentContact.contactID, Qt.resolvedUrl("ChatPage.qml"), { 'contact': currentContact }); } else { currentKey = "" diff --git a/src/ricochet-refresh/ui/MessageDelegate.qml b/src/ricochet-refresh/ui/MessageDelegate.qml new file mode 100644 index 00000000..a4dd52f1 --- /dev/null +++ b/src/ricochet-refresh/ui/MessageDelegate.qml @@ -0,0 +1,309 @@ +import QtQuick 2.15 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import im.ricochet 1.0 + +Column { + id: delegate + width: parent.width + + Loader { + active: { + if (model.section === "offline") + return true + + // either this is the first message, or the message was a long time ago.. + if ((model.timespan === -1 || + model.timespan > 3600 /* one hour */)) + return true + + return false + } + + sourceComponent: Label { + //: %1 nickname + text: { + if (model.section === "offline") + return qsTr("%1 is offline").arg(contact !== null ? contact.nickname : "") + else + return Qt.formatDateTime(model.timestamp, Qt.DefaultLocaleShortDate) + } + textFormat: Text.PlainText + width: delegate.width + elide: Text.ElideRight + horizontalAlignment: Qt.AlignHCenter + color: palette.mid + height: 28 + } + } + + Rectangle { + id: background + width: Math.max(30, message.width + 12) + height: message.height + 12 + x: model.isOutgoing ? parent.width - width - 11 : 10 + + property int __maxWidth: parent.width * 0.8 + + color: (model.status === ConversationModel.Error) ? "#ffdcc4" : ( model.isOutgoing ? "#eaeced" : "#c4e7ff" ) + Behavior on color { ColorAnimation { } } + + Rectangle { + rotation: 45 + width: 10 + height: 10 + x: model.isOutgoing ? parent.width - 20 : 10 + y: model.isOutgoing ? parent.height - 5 : -5 + color: parent.color + } + + Rectangle { + anchors.fill: parent + anchors.margins: 1 + opacity: (model.status === ConversationModel.Sending || model.status === ConversationModel.Queued || model.status === ConversationModel.Error) ? 1 : 0 + visible: opacity > 0 + color: Qt.lighter(parent.color, 1.15) + + Behavior on opacity { NumberAnimation { } } + } + + Rectangle + { + id: message + + property Item childItem: { + if (model.type == "text") + { + return textField; + } + else if (model.type =="transfer") + { + return transferField; + } + } + + width: childItem.width + height: childItem.height + x: Math.round((background.width - width) / 2) + y: 6 + + color: "transparent" + + // text message + + TextEdit { + id: textField + visible: parent.childItem === this + width: Math.min(implicitWidth, background.__maxWidth) + height: contentHeight + + renderType: Text.NativeRendering + textFormat: TextEdit.PlainText + selectionColor: palette.highlight + selectedTextColor: palette.highlightedText + font.pointSize: styleHelper.pointSize + + wrapMode: TextEdit.Wrap + readOnly: true + selectByMouse: true + text: model.text + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton + + onClicked: delegate.showContextMenu() + } + } + + // sending file transfer + Rectangle { + id: transferField + visible: parent.childItem === this + + width: 256 + height: transferDisplay.height + + color: "transparent" + + Row { + x: 0 + y: 0 + width: parent.width + height: parent.height + spacing: 6 + + Column { + id: transferDisplay + + width: parent.width - (acceptButton.visible ? (acceptButton.width + parent.spacing) : 0) - parent.spacing - cancelButton.width + spacing: 6 + + Text { + id: filename + + width: parent.width + height: styleHelper.pointSize * 1.5 + + text: model.transfer ? model.transfer.file_name : "" + font.bold: true + font.pointSize: styleHelper.pointSize + elide: Text.ElideMiddle + Accessible.role: Accessible.StaticText + Accessible.name: text + //: Description of the text displaying the filename of a file transfer, used by accessibility tech like screen readres + Accessible.description: qsTr("File transfer file name"); + } + + ProgressBar { + id: progressBar + + width: parent.width + height: visible ? 8 : 0 + + visible: model.transfer ? + (model.transfer.status === ConversationModel.Pending || + model.transfer.status === ConversationModel.InProgress || + model.transfer.status === ConversationModel.Accepted) : false + + indeterminate: model.transfer ? (model.transfer.status === ConversationModel.Pending) : true + value: model.transfer ? model.transfer.progressPercent : 0 + + Accessible.role: Accessible.ProgressBar + //: Description of progress bar displaying the file transfer progress, used by accessibility tech like screen readers + Accessible.description: qsTr("File transfer progress"); + } + + Label { + id: transferStatus + + width: parent.width + height: styleHelper.pointSize * 1.5 + + text: model.transfer ? model.transfer.statusString : "" + font.pointSize: filename.font.pointSize * 0.8; + color: Qt.lighter(filename.color, 1.5) + Accessible.role: Accessible.StaticText + Accessible.name: text + //: Description of label displaying the current status of a file transfer, used by accessibility tech like screen readers + Accessible.description: qsTr("File transfer status") + } + } + + Action { + id: downloadAction + text: qsTr("Download '%1'").arg(filename.text); + onTriggered: { + contact.conversation.tryAcceptFileTransfer(model.transfer.id); + } + } + + Button { + id: acceptButton + + visible: model.transfer ? (model.transfer.status === ConversationModel.Pending && model.transfer.direction === ConversationModel.Downloading) : false + + width: visible ? transferDisplay.height : 0 + height: visible ? transferDisplay.height : 0 + + text: "" + Accessible.role: Accessible.Button + //: Label for file transfer 'Download' button for accessibility tech like screen readers + Accessible.name: qsTr("Download") + //: Description of what the file transfer 'Download' button does for accessibility tech like screen readers + Accessible.description: qsTr("Download file") + + Image { + source: "qrc:/icons/download.png" + anchors.centerIn: parent + width: parent.width * 1/2 + height: parent.height * 1/2 + fillMode: Image.PreserveAspectFit + mipmap: true + } + action: downloadAction + } + + Action { + id: rejectFileTransferAction + text: qsTr("Reject file transfer"); + onTriggered: { + contact.conversation.rejectFileTransfer(model.transfer.id); + } + } + + Action { + id: cancelFileTransferAction + text: qsTr("Cancel file transfer"); + onTriggered: { + contact.conversation.cancelFileTransfer(model.transfer.id); + } + } + + Button { + id: cancelButton + visible: model.transfer ? + (model.transfer.status === ConversationModel.Pending || + model.transfer.status === ConversationModel.InProgress || + model.transfer.status === ConversationModel.Accepted) : false + + width: visible ? transferDisplay.height : 0 + height: visible ? transferDisplay.height : 0 + + text: "" + Accessible.role: Accessible.Button + //: Label for file transfer 'Cancel' button for accessibility tech like screen readers + Accessible.name: qsTr("Cancel or reject") + //: Description of what the file transfer 'Cancel' button does for accessibility tech like screen readers + Accessible.description: qsTr("Cancels or rejects a file transfer") + + action: acceptButton.visible ? rejectFileTransferAction : cancelFileTransferAction + Image { + source: "qrc:/icons/cancel.png" + anchors.centerIn: parent + width: parent.width * 1/2 + height: parent.height * 1/2 + fillMode: Image.PreserveAspectFit + mipmap: true + } + } + } + } + } + } + + function showContextMenu() { + var object = rightClickContextMenu.createObject(delegate, { }) + object.popupVisibleChanged.connect(function() { if (!object.visible) object.destroy(1000) }) + object.popup() + } + + Component { + id: rightClickContextMenu + + Menu { + MenuItem { + //: Text for context menu command to copy an entire message to clipboard + text: qsTr("Copy Message") + visible: textField.selectedText.length == 0 + onTriggered: { + Clipboard.copyText(textField.getText(0, textField.length)) + } + } + + Action { + id: copySelectionAction + text: qsTr("Copy Selection") + shortcut: StandardKey.Copy + onTriggered: textField.copy() + } + + MenuItem { + //: Text for context menu command to copy selected text to clipboard + text: qsTr("Copy Selection") + visible: textField.selectedText.length > 0 + action: copySelectionAction + } + } + } +} diff --git a/src/ricochet-refresh/ui/MessageDialogWrapper.qml b/src/ricochet-refresh/ui/MessageDialogWrapper.qml new file mode 100644 index 00000000..35046385 --- /dev/null +++ b/src/ricochet-refresh/ui/MessageDialogWrapper.qml @@ -0,0 +1,16 @@ +import QtQuick 2.0 +import QtQuick.Dialogs 1.1 +import "utils.js" as Utils + +MessageDialog { + id: removeContactDialog + + //: A command to remove a contact from the contact list, %1 is the contact's nickname + title: contact != null ? qsTr("Remove %1").arg(Utils.htmlEscaped(contact.nickname)) : "" + //: Confirmation of contact removal, %1 is the contact's nickname + text: contact != null ? qsTr("Do you want to permanently remove %1?").arg(Utils.htmlEscaped(contact.nickname)) : "" + //: Message describing what will happen when a contact is removed from you contact list + informativeText: qsTr("This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request.") + standardButtons: StandardButton.Yes | StandardButton.No + onYes: contact.deleteContact() +} diff --git a/src/ui/qml/NetworkSetupWizard.qml b/src/ricochet-refresh/ui/NetworkSetupWizard.qml similarity index 79% rename from src/ui/qml/NetworkSetupWizard.qml rename to src/ricochet-refresh/ui/NetworkSetupWizard.qml index 794a2f24..fd1f373a 100644 --- a/src/ui/qml/NetworkSetupWizard.qml +++ b/src/ricochet-refresh/ui/NetworkSetupWizard.qml @@ -97,13 +97,17 @@ ApplicationWindow { Label { width: parent.width + //: A label with directions for when to use the 'Connect' button text: qsTr("This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network.") wrapMode: Text.Wrap horizontalAlignment: Qt.AlignHCenter + Accessible.role: Accessible.StaticText + Accessible.name: text } Button { anchors.horizontalCenter: parent.horizontalCenter + //: Label for button to connect to the Tor network text: qsTr("Connect") isDefault: true onClicked: { @@ -111,6 +115,12 @@ ApplicationWindow { configPage.reset() configPage.save() } + Accessible.role: Accessible.Button + Accessible.name: text + Accessible.onPressAction: { + configPage.reset() + configPage.save() + } } Rectangle { @@ -121,15 +131,25 @@ ApplicationWindow { Label { width: parent.width + //: A label with directions for when to use the 'Configure' button text: qsTr("This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings.") wrapMode: Text.Wrap horizontalAlignment: Qt.AlignHCenter + Accessible.role: Accessible.StaticText + Accessible.name: text } Button { anchors.horizontalCenter: parent.horizontalCenter + //: Label for button to configure the Tor daemon beore connecting to the Tor network text: qsTr("Configure") onClicked: window.openConfig() + + Accessible.role: Accessible.Button + Accessible.name: text + Accessible.onPressAction: { + window.openConfig() + } } } } diff --git a/src/ui/qml/OfflineStateItem.qml b/src/ricochet-refresh/ui/OfflineStateItem.qml similarity index 96% rename from src/ui/qml/OfflineStateItem.qml rename to src/ricochet-refresh/ui/OfflineStateItem.qml index 7acf8b8c..246d7fb8 100644 --- a/src/ui/qml/OfflineStateItem.qml +++ b/src/ricochet-refresh/ui/OfflineStateItem.qml @@ -104,6 +104,7 @@ MouseArea { } Button { Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + //: Button label text: qsTr("Configure") onClicked: { var object = createDialog("NetworkSetupWizard.qml", { }, window) @@ -112,6 +113,7 @@ MouseArea { } Button { Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + //: Button label text: qsTr("Details") onClicked: { openPreferences("TorPreferences.qml") @@ -140,6 +142,7 @@ MouseArea { PropertyChanges { target: label + //: Label displayed when connecting to the Tor network fails text: qsTr("Connection failed") } @@ -165,7 +168,7 @@ MouseArea { PropertyChanges { target: label - //: \u2026 is ellipsis + //: Label displayed when in process of connecting, \u2026 is ellipsis text: qsTr("Connecting\u2026") } diff --git a/src/ui/qml/PageView.qml b/src/ricochet-refresh/ui/PageView.qml similarity index 100% rename from src/ui/qml/PageView.qml rename to src/ricochet-refresh/ui/PageView.qml diff --git a/src/ui/qml/PreferencesDialog.qml b/src/ricochet-refresh/ui/PreferencesDialog.qml similarity index 73% rename from src/ui/qml/PreferencesDialog.qml rename to src/ricochet-refresh/ui/PreferencesDialog.qml index 9a9a2b41..364c303f 100644 --- a/src/ui/qml/PreferencesDialog.qml +++ b/src/ricochet-refresh/ui/PreferencesDialog.qml @@ -5,8 +5,8 @@ import im.ricochet 1.0 ApplicationWindow { id: preferencesWindow - width: 550 - minimumWidth: 550 + width: 820 + minimumWidth: 820 height: 400 minimumHeight: 400 title: qsTr("Ricochet Preferences") @@ -32,27 +32,38 @@ ApplicationWindow { } } + TabView { id: tabs anchors.fill: parent anchors.margins: 8 + Accessible.role: Accessible.MenuBar + //: Name of the tab bar for accessibility tech like screen readers + Accessible.name: qsTr("Menu Tabs") + + /* QT will automatically set Accessible.text, also tabs fail to load if + * you set any accessibility properties */ Tab { + //: Title of the general settings tab title: qsTr("General") source: Qt.resolvedUrl("GeneralPreferences.qml") } Tab { + //: Title of the contacts list tab title: qsTr("Contacts") source: Qt.resolvedUrl("ContactPreferences.qml") } Tab { + //: Title of the tor tab, contains tor settings and logs title: qsTr("Tor") source: Qt.resolvedUrl("TorPreferences.qml") } Tab { + //: Title of the about tab, contains license information and ricochet version title: qsTr("About") source: Qt.resolvedUrl("AboutPreferences.qml") } diff --git a/src/ui/qml/PresenceIcon.qml b/src/ricochet-refresh/ui/PresenceIcon.qml similarity index 100% rename from src/ui/qml/PresenceIcon.qml rename to src/ricochet-refresh/ui/PresenceIcon.qml diff --git a/src/ui/qml/StartupStatusPage.qml b/src/ricochet-refresh/ui/StartupStatusPage.qml similarity index 73% rename from src/ui/qml/StartupStatusPage.qml rename to src/ricochet-refresh/ui/StartupStatusPage.qml index 5b55ba3b..9f6f5ae7 100644 --- a/src/ui/qml/StartupStatusPage.qml +++ b/src/ricochet-refresh/ui/StartupStatusPage.qml @@ -14,10 +14,13 @@ Column { right: parent.right margins: 8 } - + //: Error status string displayed when tor daemon does not launch successfully text: qsTr("The Tor process was not started successfully. This is most likely an installation or system error.") font.bold: true wrapMode: Text.Wrap + + Accessible.role: Accessible.StaticText + Accessible.name: text } Label { @@ -46,8 +49,15 @@ Column { Item { height: 1; Layout.fillWidth: true } Button { + //: Button title to quit/terminate the program text: qsTr("Quit") onClicked: Qt.quit() + + Accessible.role: Accessible.Button + Accessible.name: text + Accessible.onPressAction: { + Qt.quit() + } } } } diff --git a/src/ricochet-refresh/ui/TorBootstrapStatus.qml b/src/ricochet-refresh/ui/TorBootstrapStatus.qml new file mode 100644 index 00000000..f4d46d8a --- /dev/null +++ b/src/ricochet-refresh/ui/TorBootstrapStatus.qml @@ -0,0 +1,112 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 + +Column { + id: page + spacing: 8 + + property var bootstrap: torControl.bootstrapStatus + onBootstrapChanged: { + if (bootstrap['tag'] === "done") + window.networkReady() + } + + Label { + //: Text status displayed when tor daemon is connecting, \u2026 is ellipsis + text: qsTr("Connecting to the Tor network\u2026") + font.bold: true + + Accessible.role: Accessible.StaticText + Accessible.name: text + //: Description of the connection status text, used by accessibility tech like screen readers + Accessible.description: qsTr("Connection status"); + } + + ProgressBar { + width: parent.width + maximumValue: 100 + indeterminate: bootstrap.progress === undefined + value: bootstrap.progress === undefined ? 0 : bootstrap.progress + + Accessible.role: Accessible.ProgressBar + //: Description of the bootstrap progress bar, used by accessibility tech like screen readers + Accessible.description: qsTr("Connection progress"); + } + + Label { + text: (bootstrap['warning'] !== undefined ) ? bootstrap['warning'] : bootstrap['summary'] + textFormat: Text.PlainText + } + + TorLogDisplay { + id: logDisplay + width: parent.width + height: 0 + visible: height > 0 + + Behavior on height { + SmoothedAnimation { + easing.type: Easing.InOutQuad + velocity: 1500 + } + } + } + + RowLayout { + width: parent.width + + Button { + //: Label for button which moves us back to previous screen + text: qsTr("Back") + onClicked: window.back() + + //: Label for button which moves us back to previous screen + Accessible.name: qsTr("Back") + Accessible.onPressAction: window.back() + } + + Item { height: 1; Layout.fillWidth: true } + + Button { + text: logDisplay.height ? + //: Label for button which hides the tor logs + qsTr("Hide Details") : + //: Label for button which shows the tor logs + qsTr("Show Details") + onClicked: { + if (logDisplay.height) + logDisplay.height = 0 + else + logDisplay.height = 300 + } + + Accessible.name: text + Accessible.role: Accessible.Button + Accessible.description: logDisplay.height ? + //: Description of hide details button, used by accessibility tech like screen readers + qsTr("Hides the tor progress log") : + //: Description of show details button, used by accessibility tech like screen readers + qsTr("Shows the tor progress log") + Accessible.onPressAction: { + if (logDisplay.height) logDisplay.height = 0 + else logDisplay.height = 300 + } + } + + Item { height: 1; Layout.fillWidth: true } + + Button { + //: Label for button which closes the tor connection window + text: qsTr("Done") + isDefault: true + enabled: bootstrap.tag === "done" + onClicked: window.visible = false + + Accessible.name: text + Accessible.role: Accessible.Button + Accessible.onPressAction: window.visible = false + } + } +} + diff --git a/src/ricochet-refresh/ui/TorConfigurationPage.qml b/src/ricochet-refresh/ui/TorConfigurationPage.qml new file mode 100644 index 00000000..4656f6f0 --- /dev/null +++ b/src/ricochet-refresh/ui/TorConfigurationPage.qml @@ -0,0 +1,296 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 + +Column { + id: setup + spacing: 8 + + property alias proxyType: proxyTypeField.selectedType + property alias proxyAddress: proxyAddressField.text + property alias proxyPort: proxyPortField.text + property alias proxyUsername: proxyUsernameField.text + property alias proxyPassword: proxyPasswordField.text + property alias allowedPorts: allowedPortsField.text + property alias bridges: bridgesField.text + + function reset() { + proxyTypeField.currentIndex = 0 + proxyAddress = '' + proxyPort = '' + proxyUsername = '' + proxyPassword = '' + allowedPorts = '' + bridges = '' + } + + function save() { + var conf = {}; + conf.disableNetwork = "0"; + conf.proxyType = proxyType; + conf.proxyAddress = proxyAddress; + conf.proxyPort = proxyPort; + conf.proxyUsername = proxyUsername; + conf.proxyPassword = proxyPassword; + conf.allowedPorts = allowedPorts.trim().length > 0 ? allowedPorts.trim().split(',') : []; + conf.bridges = bridges.trim().length > 0 ? bridges.trim().split('\n') : []; + + var command = torControl.setConfiguration(conf) + command.finished.connect(function() { + if (command.successful) { + if (torControl.hasOwnership) + torControl.saveConfiguration() + window.openBootstrap() + } else + console.log("SETCONF error:", command.errorMessage) + }) + } + + Label { + width: parent.width + text: qsTr("Does this computer need a proxy to access the internet?") + wrapMode: Text.Wrap + + Accessible.role: Accessible.StaticText + Accessible.name: text + } + + GroupBox { + width: setup.width + + GridLayout { + anchors.fill: parent + columns: 2 + + /* without this the top of groupbox clips into the first row */ + Item { height: Qt.platform.os === "linux" ? 15 : 0} + Item { height: Qt.platform.os === "linux" ? 15 : 0} + + Label { + text: qsTr("Proxy type:") + color: proxyPalette.text + } + ComboBox { + id: proxyTypeField + property string none: qsTr("None") + model: [ + { "text": qsTr("None"), "type": "" }, + { "text": "SOCKS 4", "type": "socks4" }, + { "text": "SOCKS 5", "type": "socks5" }, + { "text": "HTTPS", "type": "https" }, + ] + textRole: "text" + property string selectedType: currentIndex >= 0 ? model[currentIndex].type : "" + + SystemPalette { + id: proxyPalette + colorGroup: setup.proxyType == "" ? SystemPalette.Disabled : SystemPalette.Active + } + + Accessible.role: Accessible.ComboBox + Accessible.name: selectedType + //: Description used by accessibility tech, such as screen readers + Accessible.description: qsTr("If you need a proxy to access the internet, select one from this list.") + } + + Label { + //: Label indicating the textbox to place a proxy IP or URL + text: qsTr("Address:") + color: proxyPalette.text + + Accessible.role: Accessible.StaticText + Accessible.name: text + } + RowLayout { + Layout.fillWidth: true + TextField { + id: proxyAddressField + Layout.fillWidth: true + enabled: setup.proxyType + //: Placeholder text of text box expecting an IP or URL for proxy + placeholderText: qsTr("IP address or hostname") + + Accessible.role: Accessible.EditableText + Accessible.name: placeholderText + //: Description of what to enter into the IP textbox, used by accessibility tech such as screen readers + Accessible.description: qsTr("Enter the IP address or hostname of the proxy you wish to connect to") + } + Label { + //: Label indicating the textbox to place a proxy port + text: qsTr("Port:") + color: proxyPalette.text + + } + TextField { + id: proxyPortField + Layout.preferredWidth: 50 + enabled: setup.proxyType + + Accessible.role: Accessible.EditableText + //: Name of the port label, used by accessibility tech such as screen readers + Accessible.name: qsTr("Port") + //: Description of what to enter into the Port textbox, used by accessibility tech such as screen readers + Accessible.description: qsTr("Enter the port of the proxy you wish to connect to") + } + } + + Label { + //: Label indicating the textbox to place the proxy username + text: qsTr("Username:") + color: proxyPalette.text + + Accessible.role: Accessible.StaticText + Accessible.name: text + } + RowLayout { + Layout.fillWidth: true + + TextField { + id: proxyUsernameField + Layout.fillWidth: true + enabled: setup.proxyType + //: Textbox placeholder text indicating the field is not required + placeholderText: qsTr("Optional") + + Accessible.role: Accessible.EditableText + //: Name of the username label, used by accessibility tech such as screen readers + Accessible.name: qsTr("Username") + //: Description to enter into the Username textbox, used by accessibility tech such as screen readers + Accessible.description: qsTr("If required, enter the username for the proxy you wish to connect to") + } + Label { + //: Label indicating the textbox to place the proxy password + text: qsTr("Password:") + color: proxyPalette.text + + Accessible.role: Accessible.StaticText + Accessible.name: text + } + TextField { + id: proxyPasswordField + Layout.fillWidth: true + enabled: setup.proxyType + //: Textbox placeholder text indicating the field is not required + placeholderText: qsTr("Optional") + + Accessible.role: Accessible.EditableText + //: Name of the password label, used by accessibility tech such as screen readers + Accessible.name: qsTr("Password") + //: Description to enter into the Password textbox, used by accessibility tech such as screen readers + Accessible.description: qsTr("If required, enter the password for the proxy you wish to connect to") + } + } + } + } + + Item { height: 4; width: 1 } + + Label { + width: parent.width + //: Description for the purpose of the Allowed Ports textbox + text: qsTr("Does this computer's Internet connection go through a firewall that only allows connections to certain ports?") + wrapMode: Text.Wrap + + Accessible.role: Accessible.StaticText + Accessible.name: text + } + + GroupBox { + width: parent.width + // Workaround OS X visual bug + height: Math.max(implicitHeight, 40) + + /* without this the top of groupbox clips into the first row */ + ColumnLayout { + anchors.fill: parent + + Item { height: Qt.platform.os === "linux" ? 15 : 0 } + + RowLayout { + Label { + //: Label indicating the textbox to place the allowed ports + text: qsTr("Allowed ports:") + + Accessible.role: Accessible.StaticText + Accessible.name: text + } + TextField { + id: allowedPortsField + Layout.fillWidth: true + //: Textbox showing an example entry for the firewall allowed ports entry + placeholderText: qsTr("Example: 80,443") + + Accessible.role: Accessible.EditableText + //: Name of the allowed ports label, used by accessibility tech such as screen readers + Accessible.name: qsTr("Allowed ports") // todo: translations + Accessible.description: placeholderText + } + } + } + } + + Item { height: 4; width: 1 } + + Label { + width: parent.width + + text: qsTr("If this computer's Internet connection is censored, you will need to obtain and use bridge relays.") + wrapMode: Text.Wrap + + Accessible.role: Accessible.StaticText + Accessible.name: text + } + + GroupBox { + width: parent.width + ColumnLayout { + anchors.fill: parent + + Item { height: Qt.platform.os === "linux" ? 15 : 0 } + + Label { + text: qsTr("Enter one or more bridge relays (one per line):") + + Accessible.role: Accessible.StaticText + Accessible.name: text + } + TextArea { + id: bridgesField + Layout.fillWidth: true + Layout.preferredHeight: allowedPortsField.height * 2 + tabChangesFocus: true + + Accessible.name: qsTr("Enter one or more bridge relays (one per line):") + Accessible.role: Accessible.EditableText + } + } + } + + RowLayout { + width: parent.width + + Button { + //: Button label for going back to previous screen + text: qsTr("Back") + onClicked: window.back() + + Accessible.name: text + Accessible.onPressAction: window.back() + } + + Item { height: 1; Layout.fillWidth: true } + + Button { + //: Button label for connecting to tor + text: qsTr("Connect") + isDefault: true + enabled: setup.proxyType ? (proxyAddressField.text && proxyPortField.text) : true + onClicked: { + setup.save() + } + + Accessible.name: text + Accessible.onPressAction: setup.save() + } + } +} diff --git a/src/ricochet-refresh/ui/TorLogDisplay.qml b/src/ricochet-refresh/ui/TorLogDisplay.qml new file mode 100644 index 00000000..b1cb46f6 --- /dev/null +++ b/src/ricochet-refresh/ui/TorLogDisplay.qml @@ -0,0 +1,22 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.0 + +TextArea { + id: logDisplay + readOnly: true + text: torInstance.logMessages.join('\n') + textFormat: TextEdit.PlainText + wrapMode: TextEdit.Wrap + + Connections { + target: torInstance + function onLogMessage(message) { + logDisplay.append(message) + } + } + + //: Name of the text field containg the tor logs, used by accessibility tech such as screen readers + Accessible.name: qsTr("Tor log") + Accessible.description: text // XXX: seems like a bad idea to have the entire log read out +} diff --git a/src/ricochet-refresh/ui/TorPreferences.qml b/src/ricochet-refresh/ui/TorPreferences.qml new file mode 100644 index 00000000..3c95122a --- /dev/null +++ b/src/ricochet-refresh/ui/TorPreferences.qml @@ -0,0 +1,155 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 +import im.ricochet 1.0 +import "utils.js" as Utils + +Item { + anchors.fill: parent + + property var bootstrap: torControl.bootstrapStatus + + Column { + id: info + anchors { + left: parent.left + right: parent.right + top: parent.top + margins: 8 + } + spacing: 6 + + GridLayout { + columns: 4 + + width: parent.width + Label { + //: Display label that beside it indicates whether tor is running + text: qsTr("Running:") + Accessible.role: Accessible.StaticText + Accessible.name: qsTr("Running") + } + Label { + font.bold: true + Layout.fillWidth: true + text: qsTr(torInstance.running) + + Accessible.role: Accessible.StaticText + Accessible.name: text + //: Description of the value of this label, used by acccessibility tech like screen readers + Accessible.description: qsTr("Whether tor is running") + } + Label { + //: Display label that beside it indicates whether ricochet is connected to tor's control port + text: qsTr("Control connected:") + Accessible.role: Accessible.StaticText + Accessible.name: qsTr("Control connected") + } + Label { + font.bold: true + Layout.fillWidth: true + text: ((torControl.status == TorControl.Connected) ? qsTr("Yes") : qsTr("No")) + + Accessible.role: Accessible.StaticText + Accessible.name: text + //: Description of the value of this label, used by acccessibility tech like screen readers + Accessible.description: qsTr("Whether tor control connected") + } + Label { + text: qsTr("Circuits established:") + Accessible.role: Accessible.StaticText + Accessible.name: qsTr("Circuits established") + } + Label { + font.bold: true + text: ((torControl.torStatus == TorControl.TorReady) ? qsTr("Yes") : qsTr("No")) + + Accessible.role: Accessible.StaticText + Accessible.name: text + //: Description of the value of this label, used by acccessibility tech like screen readers + Accessible.description: qsTr("Whether circuits established") + } + Label { + text: qsTr("Hidden service:") + Accessible.role: Accessible.StaticText + Accessible.name: qsTr("Hidden service") + } + Label { + font.bold: true + text: (userIdentity.isOnline ? qsTr("Online") : qsTr("Offline")) + + Accessible.role: Accessible.StaticText + Accessible.name: text + //: Description of the value of this label, used by acccessibility tech like screen readers + Accessible.description: qsTr("Whether a hidden service is up or not") + } + Label { + text: qsTr("Version:") + Accessible.role: Accessible.StaticText + Accessible.name: qsTr("Version") + } + Label { + font.bold: true + text: torControl.torVersion + textFormat: Text.PlainText + } + } + + Rectangle { + width: parent.width + height: 1 + color: palette.mid + } + + Label { + text: bootstrap.summary + visible: bootstrap.tag !== 'done' + } + + ProgressBar { + width: parent.width + maximumValue: 100 + indeterminate: bootstrap.progress === undefined + value: bootstrap.progress === undefined ? 0 : bootstrap.progress + visible: bootstrap.tag !== 'done' + } + + Label { + //: %1 is error message + text: qsTr("Error: %1").arg(Utils.htmlEscaped(errorMessage)) + visible: errorMessage != "" + + property string errorMessage: { + if (torInstance.hasError) + return torInstance.errorMessage + else if (torControl.errorMessage != "") + return torControl.errorMessage + else if (bootstrap.warning !== undefined) + return bootstrap.warning + else + return "" + } + } + + Button { + text: qsTr("Configure") + visible: torControl.hasOwnership + onClicked: { + var object = createDialog("NetworkSetupWizard.qml") + object.visible = true + } + } + } + + TorLogDisplay { + anchors { + left: parent.left + right: parent.right + top: info.bottom + bottom: parent.bottom + margins: 8 + } + visible: torInstance.running != "External" + } +} + diff --git a/src/ui/qml/TorStateWidget.qml b/src/ricochet-refresh/ui/TorStateWidget.qml similarity index 89% rename from src/ui/qml/TorStateWidget.qml rename to src/ricochet-refresh/ui/TorStateWidget.qml index e8def82e..c74b9f4b 100644 --- a/src/ui/qml/TorStateWidget.qml +++ b/src/ricochet-refresh/ui/TorStateWidget.qml @@ -34,4 +34,8 @@ Label { return qsTr("Connected") } } + + Accessible.name: qsTr("Tor status") // todo: translation + Accessible.role: Accessible.StaticText + Accessible.description: text } diff --git a/src/ui/qml/UnreadCountBadge.qml b/src/ricochet-refresh/ui/UnreadCountBadge.qml similarity index 100% rename from src/ui/qml/UnreadCountBadge.qml rename to src/ricochet-refresh/ui/UnreadCountBadge.qml diff --git a/src/ui/qml/dummy.qml b/src/ricochet-refresh/ui/dummy.qml similarity index 100% rename from src/ui/qml/dummy.qml rename to src/ricochet-refresh/ui/dummy.qml diff --git a/src/ui/qml/main.qml b/src/ricochet-refresh/ui/main.qml similarity index 88% rename from src/ui/qml/main.qml rename to src/ricochet-refresh/ui/main.qml index 71683303..de1b24ba 100644 --- a/src/ui/qml/main.qml +++ b/src/ricochet-refresh/ui/main.qml @@ -77,8 +77,8 @@ QtObject { property list data: [ Connections { - target: userIdentity.contacts.incomingRequests - onRequestAdded: { + target: userIdentity + function onRequestAdded(request) { var object = createDialog("ContactRequestDialog.qml", { 'request': request }) object.visible = true } @@ -86,7 +86,7 @@ QtObject { Connections { target: torInstance - onConfigurationNeededChanged: { + function onConfigurationNeededChanged() { if (torInstance.configurationNeeded) { var object = createDialog("NetworkSetupWizard.qml", { 'modality': Qt.ApplicationModal }, mainWindow) object.networkReady.connect(function() { object.visible = false }) @@ -120,19 +120,6 @@ QtObject { property int dialogWindowFlags: Qt.Dialog | Qt.WindowSystemMenuHint | Qt.WindowTitleHint | Qt.WindowCloseButtonHint }, - Timer { - interval: 2000 - running: true - repeat: false - onTriggered: { - var pendingRequests = userIdentity.contacts.incomingRequests.requests - for (var i = 0; i < pendingRequests.length; i++) { - var object = createDialog("ContactRequestDialog.qml", { 'request': pendingRequests[i] }) - object.visible = true - } - } - }, - Loader { id: audioNotificationLoader active: uiSettings.data.playAudioNotification || false diff --git a/src/ui/qml/qml.qrc b/src/ricochet-refresh/ui/qml.qrc similarity index 94% rename from src/ui/qml/qml.qrc rename to src/ricochet-refresh/ui/qml.qrc index 0ea0ed53..b286a49e 100644 --- a/src/ui/qml/qml.qrc +++ b/src/ricochet-refresh/ui/qml.qrc @@ -26,7 +26,6 @@ StartupStatusPage.qml OfflineStateItem.qml MainWindow.qml - OpenBrowserDialog.qml Bubble.qml PageView.qml ChatPage.qml @@ -37,6 +36,6 @@ utils.js - ../../../LICENSE + ../../LICENSE diff --git a/src/ui/qml/utils.js b/src/ricochet-refresh/ui/utils.js similarity index 100% rename from src/ui/qml/utils.js rename to src/ricochet-refresh/ui/utils.js diff --git a/src/ricochet.desktop b/src/ricochet.desktop deleted file mode 100644 index 357afde9..00000000 --- a/src/ricochet.desktop +++ /dev/null @@ -1,9 +0,0 @@ -[Desktop Entry] -Encoding=UTF-8 -Type=Application -Name=Ricochet IM -Categories=Network;InstantMessaging;Chat;FileTransfer;Qt -Comment=Anonymous instant messaging -Icon=ricochet -Exec=ricochet - diff --git a/src/ui/qml/AboutPreferences.qml b/src/ui/qml/AboutPreferences.qml deleted file mode 100644 index 088880d1..00000000 --- a/src/ui/qml/AboutPreferences.qml +++ /dev/null @@ -1,36 +0,0 @@ -import QtQuick 2.0 -import QtQuick.Controls 1.0 -import QtQuick.Layouts 1.0 - -ColumnLayout { - anchors { - fill: parent - margins: 8 - } - - Label { - Layout.fillWidth: true - //: %1 version, e.g. 1.0.0 - text: qsTr("Ricochet %1").arg(uiMain.version) - horizontalAlignment: Qt.AlignHCenter - } - - Label { - Layout.fillWidth: true - text: "ricochet.im" - horizontalAlignment: Qt.AlignHCenter - - MouseArea { - anchors.fill: parent - onClicked: Qt.openUrlExternally("https://ricochet.im/") - } - } - - TextArea { - Layout.fillHeight: true - Layout.fillWidth: true - readOnly: true - text: uiMain.aboutText - } -} - diff --git a/src/ui/qml/ContactActions.qml b/src/ui/qml/ContactActions.qml deleted file mode 100644 index 3eab6e25..00000000 --- a/src/ui/qml/ContactActions.qml +++ /dev/null @@ -1,64 +0,0 @@ -import QtQuick 2.0 -import QtQuick.Controls 1.0 -import QtQuick.Dialogs 1.1 -import "ContactWindow.js" as ContactWindow - -Item { - id: contactMenu - - property QtObject contact - - function openWindow() { - var window = ContactWindow.getWindow(contact) - window.raise() - window.requestActivate() - } - - function removeContact() { - removeContactDialog.active = true - if (removeContactDialog.item !== null) { - removeContactDialog.item.open() - } else if (uiMain.showRemoveContactDialog(contact)) { - contact.deleteContact() - } - } - - function openContextMenu() { - contextMenu.popup() - } - - function openPreferences() { - root.openPreferences("ContactPreferences.qml", { 'selectedContact': contact }) - } - - signal renameTriggered - - Menu { - id: contextMenu - - MenuItem { - text: qsTr("Open Window") - onTriggered: openWindow() - } - MenuItem { - text: qsTr("Details...") - onTriggered: openPreferences() - } - MenuItem { - text: qsTr("Rename") - onTriggered: renameTriggered() - } - MenuSeparator { } - MenuItem { - text: qsTr("Remove") - onTriggered: removeContact() - } - } - - Loader { - id: removeContactDialog - source: "MessageDialogWrapper.qml" - active: false - } -} - diff --git a/src/ui/qml/MessageDelegate.qml b/src/ui/qml/MessageDelegate.qml deleted file mode 100644 index 869ce66c..00000000 --- a/src/ui/qml/MessageDelegate.qml +++ /dev/null @@ -1,180 +0,0 @@ -import QtQuick 2.0 -import QtQuick.Controls 1.0 -import im.ricochet 1.0 - -Column { - id: delegate - width: parent.width - - Loader { - active: { - if (model.section === "offline") - return true - - // either this is the first message, or the message was a long time ago.. - if ((model.timespan === -1 || - model.timespan > 3600 /* one hour */)) - return true - - return false - } - - sourceComponent: Label { - //: %1 nickname - text: { - if (model.section === "offline") - return qsTr("%1 is offline").arg(contact !== null ? contact.nickname : "") - else - return Qt.formatDateTime(model.timestamp, Qt.DefaultLocaleShortDate) - } - textFormat: Text.PlainText - width: background.parent.width - elide: Text.ElideRight - horizontalAlignment: Qt.AlignHCenter - color: palette.mid - - Rectangle { - id: line - width: (parent.width - parent.contentWidth) / 2 - 4 - height: 1 - y: (parent.height - 1) / 2 - color: Qt.lighter(palette.mid, 1.4) - } - - Rectangle { - width: line.width - height: 1 - y: line.y - x: parent.width - width - color: line.color - } - } - } - - Rectangle { - id: background - width: Math.max(30, textField.width + 12) - height: textField.height + 12 - x: model.isOutgoing ? parent.width - width - 11 : 10 - - property int __maxWidth: parent.width * 0.8 - - color: (model.status === ConversationModel.Error) ? "#ffdcc4" : ( model.isOutgoing ? "#eaeced" : "#c4e7ff" ) - Behavior on color { ColorAnimation { } } - - Rectangle { - rotation: 45 - width: 10 - height: 10 - x: model.isOutgoing ? parent.width - 20 : 10 - y: model.isOutgoing ? parent.height - 5 : -5 - color: parent.color - } - - Rectangle { - anchors.fill: parent - anchors.margins: 1 - opacity: (model.status === ConversationModel.Sending || model.status === ConversationModel.Queued || model.status === ConversationModel.Error) ? 1 : 0 - visible: opacity > 0 - color: Qt.lighter(parent.color, 1.15) - - Behavior on opacity { NumberAnimation { } } - } - - TextEdit { - id: textField - width: Math.min(implicitWidth, background.__maxWidth) - height: contentHeight - x: Math.round((parent.width - width) / 2) - y: 6 - - renderType: Text.NativeRendering - textFormat: TextEdit.RichText - selectionColor: palette.highlight - selectedTextColor: palette.highlightedText - font.pointSize: styleHelper.pointSize - - wrapMode: TextEdit.Wrap - readOnly: true - selectByMouse: true - text: LinkedText.parsed(model.text) - - onLinkActivated: { - textField.deselect() - delegate.showContextMenu(link) - } - - // Workaround an incomplete fix for QTBUG-31646 - Component.onCompleted: { - if (textField.hasOwnProperty('linkHovered')) - textField.linkHovered.connect(function() { }) - } - - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.RightButton - - onClicked: delegate.showContextMenu(parent.hoveredLink) - } - } - } - - function showContextMenu(link) { - var object = contextMenu.createObject(delegate, (link !== undefined) ? { 'hoveredLink': link } : { }) - // XXX QtQuickControls private API. The only other option is 'visible', and it is not reliable. See PR#183 - object.popupVisibleChanged.connect(function() { if (!object.__popupVisible) object.destroy(1000) }) - object.popup() - } - - Component { - id: contextMenu - - Menu { - property string hoveredLink: textField.hasOwnProperty('hoveredLink') ? textField.hoveredLink : "" - MenuItem { - text: linkAddContact.visible ? qsTr("Copy ID") : qsTr("Copy Link") - visible: hoveredLink.length > 0 - onTriggered: LinkedText.copyToClipboard(hoveredLink) - } - MenuItem { - text: qsTr("Open with Browser") - visible: hoveredLink.length > 0 && hoveredLink.substr(0,4).toLowerCase() == "http" - onTriggered: { - if (uiSettings.data.alwaysOpenBrowser || contact.settings.data.alwaysOpenBrowser) { - Qt.openUrlExternally(hoveredLink) - } else { - var window = uiMain.findParentWindow(delegate) - var object = createDialog("OpenBrowserDialog.qml", { 'link': hoveredLink, 'contact': contact }, window) - object.visible = true - } - } - } - MenuItem { - id: linkAddContact - text: qsTr("Add as Contact") - visible: hoveredLink.length > 0 && (hoveredLink.substr(0,9).toLowerCase() == "ricochet:" - || hoveredLink.substr(0,8).toLowerCase() == "torsion:") - onTriggered: { - var object = createDialog("AddContactDialog.qml", { 'staticContactId': hoveredLink }, chatWindow) - object.visible = true - } - } - MenuSeparator { - visible: hoveredLink.length > 0 - } - MenuItem { - text: qsTr("Copy Message") - visible: textField.selectedText.length == 0 - onTriggered: { - LinkedText.copyToClipboard(textField.getText(0, textField.length)) - } - } - MenuItem { - text: qsTr("Copy Selection") - visible: textField.selectedText.length > 0 - shortcut: "Ctrl+C" - onTriggered: textField.copy() - } - } - } -} diff --git a/src/ui/qml/MessageDialogWrapper.qml b/src/ui/qml/MessageDialogWrapper.qml deleted file mode 100644 index 54e04f30..00000000 --- a/src/ui/qml/MessageDialogWrapper.qml +++ /dev/null @@ -1,14 +0,0 @@ -import QtQuick 2.0 -import QtQuick.Dialogs 1.1 -import "utils.js" as Utils - -MessageDialog { - id: removeContactDialog - - title: qsTr("Remove %1").arg(Utils.htmlEscaped(contact.nickname)) - //: %1 nickname - text: qsTr("Do you want to permanently remove %1?").arg(Utils.htmlEscaped(contact.nickname)) - informativeText: qsTr("This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request.") - standardButtons: StandardButton.Yes | StandardButton.No - onYes: contact.deleteContact() -} diff --git a/src/ui/qml/OpenBrowserDialog.qml b/src/ui/qml/OpenBrowserDialog.qml deleted file mode 100644 index 8134b747..00000000 --- a/src/ui/qml/OpenBrowserDialog.qml +++ /dev/null @@ -1,93 +0,0 @@ -import QtQuick 2.0 -import QtQuick.Controls 1.0 -import QtQuick.Layouts 1.0 -import im.ricochet 1.0 -import "utils.js" as Utils - -ApplicationWindow { - id: dialog - width: 400 - height: layout.height + 32 - minimumWidth: width - maximumWidth: width - minimumHeight: height - maximumHeight: height - flags: styleHelper.dialogWindowFlags - modality: Qt.WindowModal - title: mainWindow.title - - signal closed - onVisibleChanged: if (!visible) closed() - - function close() { visible = false } - - property string link - property QtObject contact - - ColumnLayout { - id: layout - focus: true - spacing: 8 - anchors { - left: parent.left - right: parent.right - top: parent.top - margins: 16 - } - - Label { - Layout.fillWidth: true - text: qsTr("Warning! Opening links with your default browser will harm your security and anonymity.

You can copy to the clipboard instead.") - wrapMode: Text.Wrap - horizontalAlignment: Qt.AlignHCenter - onLinkActivated: { - LinkedText.copyToClipboard(dialog.link) - dialog.close() - } - } - - Item { width: 1; height: 1 } - - Rectangle { - height: 1 - Layout.fillWidth: true - color: Qt.darker(palette.window, 1.5) - } - - CheckBox { - id: alwaysOpenContact - text: qsTr("Don't ask again for links from %1").arg(contact ? Utils.htmlEscaped(contact.nickname) : "???") - checked: contact.settings.data.alwaysOpenBrowser || false - } - - CheckBox { - id: alwaysOpenAll - text: qsTr("Don't ask again for any links (not recommended!)") - checked: uiSettings.data.alwaysOpenBrowser || false - } - - RowLayout { - width: parent.width - Button { - text: qsTr("Open Browser") - onClicked: { - if (alwaysOpenContact.checked) - contact.settings.write("alwaysOpenBrowser", true) - if (alwaysOpenAll.checked) - uiSettings.write("alwaysOpenBrowser", true) - Qt.openUrlExternally(link) - dialog.close() - } - } - Item { Layout.fillWidth: true; height: 1 } - Button { - text: qsTr("Cancel") - isDefault: true - onClicked: dialog.close() - } - } - - Keys.onEscapePressed: dialog.close() - Keys.onReturnPressed: dialog.close() - } -} diff --git a/src/ui/qml/TorBootstrapStatus.qml b/src/ui/qml/TorBootstrapStatus.qml deleted file mode 100644 index 389a9738..00000000 --- a/src/ui/qml/TorBootstrapStatus.qml +++ /dev/null @@ -1,77 +0,0 @@ -import QtQuick 2.0 -import QtQuick.Controls 1.0 -import QtQuick.Layouts 1.0 - -Column { - id: page - spacing: 8 - - property var bootstrap: torControl.bootstrapStatus - onBootstrapChanged: { - if (bootstrap['tag'] === "done") - window.networkReady() - } - - Label { - //: \u2026 is ellipsis - text: qsTr("Connecting to the Tor network\u2026") - font.bold: true - } - - ProgressBar { - width: parent.width - maximumValue: 100 - indeterminate: bootstrap.progress === undefined - value: bootstrap.progress === undefined ? 0 : bootstrap.progress - } - - Label { - text: (bootstrap['warning'] !== undefined ) ? bootstrap['warning'] : bootstrap['summary'] - textFormat: Text.PlainText - } - - TorLogDisplay { - id: logDisplay - width: parent.width - height: 0 - visible: height > 0 - - Behavior on height { - SmoothedAnimation { - easing.type: Easing.InOutQuad - velocity: 1500 - } - } - } - - RowLayout { - width: parent.width - - Button { - text: qsTr("Back") - onClicked: window.back() - } - - Item { height: 1; Layout.fillWidth: true } - - Button { - text: logDisplay.height ? qsTr("Hide details") : qsTr("Show details") - onClicked: { - if (logDisplay.height) - logDisplay.height = 0 - else - logDisplay.height = 300 - } - } - - Item { height: 1; Layout.fillWidth: true } - - Button { - text: qsTr("Done") - isDefault: true - enabled: bootstrap.tag === "done" - onClicked: window.visible = false - } - } -} - diff --git a/src/ui/qml/TorConfigurationPage.qml b/src/ui/qml/TorConfigurationPage.qml deleted file mode 100644 index 15262e53..00000000 --- a/src/ui/qml/TorConfigurationPage.qml +++ /dev/null @@ -1,236 +0,0 @@ -import QtQuick 2.0 -import QtQuick.Controls 1.0 -import QtQuick.Layouts 1.0 - -Column { - id: setup - spacing: 8 - - property alias proxyType: proxyTypeField.selectedType - property alias proxyAddress: proxyAddressField.text - property alias proxyPort: proxyPortField.text - property alias proxyUsername: proxyUsernameField.text - property alias proxyPassword: proxyPasswordField.text - property alias allowedPorts: allowedPortsField.text - property alias bridges: bridgesField.text - - function reset() { - proxyTypeField.currentIndex = 0 - proxyAddress = '' - proxyPort = '' - proxyUsername = '' - proxyPassword = '' - allowedPorts = '' - bridges = '' - } - - function save() { - // null value is reset - var conf = { - 'Socks4Proxy': null, 'Socks5Proxy': null, 'Socks5ProxyUsername': null, - 'Socks5ProxyPassword': null, 'HTTPSProxy': null, 'HTTPSProxyAuthenticator': null, - 'ReachableAddresses': null, 'Bridge': null, 'UseBridges': null, 'DisableNetwork': '0', - // These are not set anymore, but are included here to clear old configurations - 'FascistFirewall': null, 'FirewallPorts': null - } - - if (proxyType === "socks4") { - conf['Socks4Proxy'] = proxyAddress + ":" + proxyPort - } else if (proxyType === "socks5") { - conf['Socks5Proxy'] = proxyAddress + ":" + proxyPort - if (proxyUsername.length > 0) - conf['Socks5ProxyUsername'] = proxyUsername - if (proxyPassword.length > 0) - conf['Socks5ProxyPassword'] = proxyPassword - } else if (proxyType === "https") { - conf['HTTPSProxy'] = proxyAddress + ":" + proxyPort - if (proxyUsername.length > 0 || proxyPassword.length > 0) - conf['HTTPSProxyAuthenticator'] = proxyUsername + ":" + proxyPassword - } - - if (allowedPorts.length > 0) { - // Prepend *: to port-only fields - var ports = allowedPorts.split(',') - for (var i = 0; i < ports.length; i++) { - ports[i] = ports[i].trim() - if (ports[i].indexOf(':') < 0 && ports[i].indexOf('.') < 0) { - ports[i] = "*:" + ports[i] - } - } - - conf['ReachableAddresses'] = ports.join(', ') - } - - if (bridges.length > 0) { - conf['Bridge'] = bridges.split('\n') - conf['UseBridges'] = "1" - } - - var command = torControl.setConfiguration(conf) - command.finished.connect(function() { - if (command.successful) { - if (torControl.hasOwnership) - torControl.saveConfiguration() - window.openBootstrap() - } else - console.log("SETCONF error:", command.errorMessage) - }) - } - - Label { - width: parent.width - text: qsTr("Does this computer need a proxy to access the internet?") - wrapMode: Text.Wrap - } - - GroupBox { - width: setup.width - - GridLayout { - anchors.fill: parent - columns: 2 - - Label { - text: qsTr("Proxy type:") - color: proxyPalette.text - } - ComboBox { - id: proxyTypeField - property string none: qsTr("None") - model: [ - { "text": qsTr("None"), "type": "" }, - { "text": "SOCKS 4", "type": "socks4" }, - { "text": "SOCKS 5", "type": "socks5" }, - { "text": "HTTPS", "type": "https" }, - ] - textRole: "text" - property string selectedType: currentIndex >= 0 ? model[currentIndex].type : "" - - SystemPalette { - id: proxyPalette - colorGroup: setup.proxyType == "" ? SystemPalette.Disabled : SystemPalette.Active - } - } - - Label { - text: qsTr("Address:") - color: proxyPalette.text - } - RowLayout { - Layout.fillWidth: true - TextField { - id: proxyAddressField - Layout.fillWidth: true - enabled: setup.proxyType - placeholderText: qsTr("IP address or hostname") - } - Label { - text: qsTr("Port:") - color: proxyPalette.text - } - TextField { - id: proxyPortField - Layout.preferredWidth: 50 - enabled: setup.proxyType - } - } - - Label { - text: qsTr("Username:") - color: proxyPalette.text - } - RowLayout { - Layout.fillWidth: true - - TextField { - id: proxyUsernameField - Layout.fillWidth: true - enabled: setup.proxyType - placeholderText: qsTr("Optional") - } - Label { - text: qsTr("Password:") - color: proxyPalette.text - } - TextField { - id: proxyPasswordField - Layout.fillWidth: true - enabled: setup.proxyType - placeholderText: qsTr("Optional") - } - } - } - } - - Item { height: 4; width: 1 } - - Label { - width: parent.width - text: qsTr("Does this computer's Internet connection go through a firewall that only allows connections to certain ports?") - wrapMode: Text.Wrap - } - - GroupBox { - width: parent.width - // Workaround OS X visual bug - height: Math.max(implicitHeight, 40) - RowLayout { - anchors.fill: parent - Label { - text: qsTr("Allowed ports:") - } - TextField { - id: allowedPortsField - Layout.fillWidth: true - } - Label { - text: qsTr("Example: 80,443") - SystemPalette { id: disabledPalette; colorGroup: SystemPalette.Disabled } - color: disabledPalette.text - } - } - } - - Item { height: 4; width: 1 } - - Label { - width: parent.width - text: qsTr("If this computer's Internet connection is censored, you will need to obtain and use bridge relays.") - wrapMode: Text.Wrap - } - - GroupBox { - width: parent.width - ColumnLayout { - anchors.fill: parent - Label { - text: qsTr("Enter one or more bridge relays (one per line):") - } - TextArea { - id: bridgesField - Layout.fillWidth: true - Layout.preferredHeight: allowedPortsField.height * 2 - tabChangesFocus: true - } - } - } - - RowLayout { - width: parent.width - - Button { - text: qsTr("Back") - onClicked: window.back() - } - - Item { height: 1; Layout.fillWidth: true } - - Button { - text: qsTr("Connect") - isDefault: true - onClicked: { - setup.save() - } - } - } -} diff --git a/src/ui/qml/TorLogDisplay.qml b/src/ui/qml/TorLogDisplay.qml deleted file mode 100644 index a1b96d08..00000000 --- a/src/ui/qml/TorLogDisplay.qml +++ /dev/null @@ -1,18 +0,0 @@ -import QtQuick 2.0 -import QtQuick.Controls 1.0 -import QtQuick.Layouts 1.0 - -TextArea { - id: logDisplay - readOnly: true - text: torInstance.logMessages.join('\n') - textFormat: TextEdit.PlainText - wrapMode: TextEdit.Wrap - - Connections { - target: torInstance.process - onLogMessage: { - logDisplay.append(message) - } - } -} diff --git a/src/ui/qml/TorPreferences.qml b/src/ui/qml/TorPreferences.qml deleted file mode 100644 index 273b3f9d..00000000 --- a/src/ui/qml/TorPreferences.qml +++ /dev/null @@ -1,96 +0,0 @@ -import QtQuick 2.0 -import QtQuick.Controls 1.0 -import QtQuick.Layouts 1.0 -import im.ricochet 1.0 -import "utils.js" as Utils - -Item { - anchors.fill: parent - - property var bootstrap: torInstance.control.bootstrapStatus - - Column { - id: info - anchors { - left: parent.left - right: parent.right - top: parent.top - margins: 8 - } - spacing: 6 - - GridLayout { - columns: 4 - width: parent.width - Label { text: qsTr("Running:") } - Label { font.bold: true; Layout.fillWidth: true; text: (torInstance.process ? (torInstance.process.state == TorProcess.Ready ? qsTr("Yes") : qsTr("No")) : qsTr("External")) } - Label { text: qsTr("Control connected:") } - Label { font.bold: true; Layout.fillWidth: true; text: ((torInstance.control.status == TorControl.Connected) ? qsTr("Yes") : qsTr("No")) } - Label { text: qsTr("Circuits established:") } - Label { font.bold: true; text: ((torInstance.control.torStatus == TorControl.TorReady) ? qsTr("Yes") : qsTr("No")) } - Label { text: qsTr("Hidden service:") } - Label { font.bold: true; text: (userIdentity.isOnline ? qsTr("Online") : qsTr("Offline")) } - Label { text: qsTr("Version:") } - Label { font.bold: true; text: torControl.torVersion; textFormat: Text.PlainText } - //Label { text: "Recommended:" } - //Label { font.bold: true; text: "Unknown" } - } - - Rectangle { - width: parent.width - height: 1 - color: palette.mid - } - - Label { - text: bootstrap.summary - visible: bootstrap.tag !== 'done' - } - - ProgressBar { - width: parent.width - maximumValue: 100 - indeterminate: bootstrap.progress === undefined - value: bootstrap.progress === undefined ? 0 : bootstrap.progress - visible: bootstrap.tag !== 'done' - } - - Label { - //: %1 is error message - text: qsTr("Error: %1").arg(Utils.htmlEscaped(errorMessage)) - visible: errorMessage != "" - - property string errorMessage: { - if (torInstance.hasError) - return torInstance.errorMessage - else if (torInstance.control.errorMessage != "") - return torInstance.control.errorMessage - else if (bootstrap.warning !== undefined) - return bootstrap.warning - else - return "" - } - } - - Button { - text: qsTr("Configure") - visible: torControl.hasOwnership - onClicked: { - var object = createDialog("NetworkSetupWizard.qml") - object.visible = true - } - } - } - - TorLogDisplay { - anchors { - left: parent.left - right: parent.right - top: info.bottom - bottom: parent.bottom - margins: 8 - } - visible: torInstance.process !== null - } -} - diff --git a/src/utils/CryptoKey.cpp b/src/utils/CryptoKey.cpp deleted file mode 100644 index 8aa03575..00000000 --- a/src/utils/CryptoKey.cpp +++ /dev/null @@ -1,477 +0,0 @@ -/* Ricochet - https://ricochet.im/ - * Copyright (C) 2014, John Brooks - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * - * * Neither the names of the copyright owners nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "CryptoKey.h" -#include "SecureRNG.h" -#include "Useful.h" -#include -#include -#include -#include -#include - -#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) -void RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q) -{ - *p = r->p; - *q = r->q; -} -#define RSA_bits(o) (BN_num_bits((o)->n)) -#endif - -void base32_encode(char *dest, unsigned destlen, const char *src, unsigned srclen); -bool base32_decode(char *dest, unsigned destlen, const char *src, unsigned srclen); - -CryptoKey::CryptoKey() -{ -} - -CryptoKey::~CryptoKey() -{ - clear(); -} - -CryptoKey::Data::~Data() -{ - if (key) - { - RSA_free(key); - key = 0; - } -} - -void CryptoKey::clear() -{ - d = 0; -} - -bool CryptoKey::loadFromData(const QByteArray &data, KeyType type, KeyFormat format) -{ - RSA *key = NULL; - clear(); - - if (data.isEmpty()) - return false; - - if (format == PEM) { - BIO *b = BIO_new_mem_buf((void*)data.constData(), -1); - - if (type == PrivateKey) - key = PEM_read_bio_RSAPrivateKey(b, NULL, NULL, NULL); - else - key = PEM_read_bio_RSAPublicKey(b, NULL, NULL, NULL); - - BIO_free(b); - } else if (format == DER) { - const uchar *dp = reinterpret_cast(data.constData()); - - if (type == PrivateKey) - key = d2i_RSAPrivateKey(NULL, &dp, data.size()); - else - key = d2i_RSAPublicKey(NULL, &dp, data.size()); - } else { - Q_UNREACHABLE(); - } - - if (!key) { - qWarning() << "Failed to parse" << (type == PrivateKey ? "private" : "public") << "key from data"; - return false; - } - - d = new Data(key); - return true; -} - -bool CryptoKey::loadFromFile(const QString &path, KeyType type, KeyFormat format) -{ - QFile file(path); - if (!file.open(QIODevice::ReadOnly)) - { - qWarning() << "Failed to open" << (type == PrivateKey ? "private" : "public") << "key from" - << path << "-" << file.errorString(); - return false; - } - - QByteArray data = file.readAll(); - file.close(); - - return loadFromData(data, type, format); -} - -bool CryptoKey::isPrivate() const -{ - if (!isLoaded()) { - return false; - } else { - const BIGNUM *p, *q; - RSA_get0_factors(d->key, &p, &q); - return (p != 0); - } -} - -int CryptoKey::bits() const -{ - return isLoaded() ? RSA_bits(d->key) : 0; -} - -QByteArray CryptoKey::publicKeyDigest() const -{ - if (!isLoaded()) - return QByteArray(); - - QByteArray buf = encodedPublicKey(DER); - - QByteArray re(20, 0); - bool ok = SHA1(reinterpret_cast(buf.constData()), buf.size(), - reinterpret_cast(re.data())) != NULL; - - if (!ok) - { - qWarning() << "Failed to hash public key data for digest"; - return QByteArray(); - } - - return re; -} - -QByteArray CryptoKey::encodedPublicKey(KeyFormat format) const -{ - if (!isLoaded()) - return QByteArray(); - - if (format == PEM) { - BIO *b = BIO_new(BIO_s_mem()); - - if (!PEM_write_bio_RSAPublicKey(b, d->key)) { - BUG() << "Failed to encode public key in PEM format"; - BIO_free(b); - return QByteArray(); - } - - BUF_MEM *buf; - BIO_get_mem_ptr(b, &buf); - - /* Close BIO, but don't free buf. */ - (void)BIO_set_close(b, BIO_NOCLOSE); - BIO_free(b); - - QByteArray re((const char *)buf->data, (int)buf->length); - BUF_MEM_free(buf); - return re; - } else if (format == DER) { - uchar *buf = NULL; - int len = i2d_RSAPublicKey(d->key, &buf); - if (len <= 0 || !buf) { - BUG() << "Failed to encode public key in DER format"; - return QByteArray(); - } - - QByteArray re((const char*)buf, len); - OPENSSL_free(buf); - return re; - } else { - Q_UNREACHABLE(); - } - - return QByteArray(); -} - -QByteArray CryptoKey::encodedPrivateKey(KeyFormat format) const -{ - if (!isLoaded() || !isPrivate()) - return QByteArray(); - - if (format == PEM) { - BIO *b = BIO_new(BIO_s_mem()); - - if (!PEM_write_bio_RSAPrivateKey(b, d->key, NULL, NULL, 0, NULL, NULL)) { - BUG() << "Failed to encode private key in PEM format"; - BIO_free(b); - return QByteArray(); - } - - BUF_MEM *buf; - BIO_get_mem_ptr(b, &buf); - - /* Close BIO, but don't free buf. */ - (void)BIO_set_close(b, BIO_NOCLOSE); - BIO_free(b); - - QByteArray re((const char *)buf->data, (int)buf->length); - BUF_MEM_free(buf); - return re; - } else if (format == DER) { - uchar *buf = NULL; - int len = i2d_RSAPrivateKey(d->key, &buf); - if (len <= 0 || !buf) { - BUG() << "Failed to encode private key in DER format"; - return QByteArray(); - } - - QByteArray re((const char*)buf, len); - OPENSSL_free(buf); - return re; - } else { - Q_UNREACHABLE(); - } - - return QByteArray(); -} - -QString CryptoKey::torServiceID() const -{ - if (!isLoaded()) - return QString(); - - QByteArray digest = publicKeyDigest(); - if (digest.isNull()) - return QString(); - - static const int hostnameDigestSize = 10; - static const int hostnameEncodedSize = 16; - - QByteArray re(hostnameEncodedSize+1, 0); - base32_encode(re.data(), re.size(), digest.constData(), hostnameDigestSize); - - // Chop extra null byte - re.chop(1); - - return QString::fromLatin1(re); -} - -QByteArray CryptoKey::signData(const QByteArray &data) const -{ - QByteArray digest(32, 0); - bool ok = SHA256(reinterpret_cast(data.constData()), data.size(), - reinterpret_cast(digest.data())) != NULL; - if (!ok) { - qWarning() << "Digest for RSA signature failed"; - return QByteArray(); - } - - return signSHA256(digest); -} - -QByteArray CryptoKey::signSHA256(const QByteArray &digest) const -{ - if (!isPrivate()) - return QByteArray(); - - QByteArray re(RSA_size(d->key), 0); - unsigned sigsize = 0; - int r = RSA_sign(NID_sha256, reinterpret_cast(digest.constData()), digest.size(), - reinterpret_cast(re.data()), &sigsize, d->key); - - if (r != 1) { - qWarning() << "RSA encryption failed when generating signature"; - return QByteArray(); - } - - re.truncate(sigsize); - return re; -} - -bool CryptoKey::verifyData(const QByteArray &data, QByteArray signature) const -{ - QByteArray digest(32, 0); - bool ok = SHA256(reinterpret_cast(data.constData()), data.size(), - reinterpret_cast(digest.data())) != NULL; - - if (!ok) { - qWarning() << "Digest for RSA verify failed"; - return false; - } - - return verifySHA256(digest, signature); -} - -bool CryptoKey::verifySHA256(const QByteArray &digest, QByteArray signature) const -{ - if (!isLoaded()) - return false; - - int r = RSA_verify(NID_sha256, reinterpret_cast(digest.constData()), digest.size(), - reinterpret_cast(signature.data()), signature.size(), d->key); - if (r != 1) - return false; - return true; -} - -/* Cryptographic hash of a password as expected by Tor's HashedControlPassword */ -QByteArray torControlHashedPassword(const QByteArray &password) -{ - QByteArray salt = SecureRNG::random(8); - if (salt.isNull()) - return QByteArray(); - - int count = ((quint32)16 + (96 & 15)) << ((96 >> 4) + 6); - - SHA_CTX hash; - SHA1_Init(&hash); - - QByteArray tmp = salt + password; - while (count) - { - int c = qMin(count, tmp.size()); - SHA1_Update(&hash, reinterpret_cast(tmp.constData()), c); - count -= c; - } - - unsigned char md[20]; - SHA1_Final(md, &hash); - - /* 60 is the hex-encoded value of 96, which is a constant used by Tor's algorithm. */ - return QByteArray("16:") + salt.toHex().toUpper() + QByteArray("60") + - QByteArray::fromRawData(reinterpret_cast(md), 20).toHex().toUpper(); -} - -/* Copyright (c) 2001-2004, Roger Dingledine - * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson - * Copyright (c) 2007-2010, The Tor Project, Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * - * Neither the names of the copyright owners nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#define BASE32_CHARS "abcdefghijklmnopqrstuvwxyz234567" - -/* Implements base32 encoding as in rfc3548. Requires that srclen*8 is a multiple of 5. */ -void base32_encode(char *dest, unsigned destlen, const char *src, unsigned srclen) -{ - unsigned i, bit, v, u; - unsigned nbits = srclen * 8; - - /* We need an even multiple of 5 bits, and enough space */ - if ((nbits%5) != 0 || destlen > (nbits/5)+1) { - Q_ASSERT(false); - memset(dest, 0, destlen); - return; - } - - for (i = 0, bit = 0; bit < nbits; ++i, bit += 5) - { - /* set v to the 16-bit value starting at src[bits/8], 0-padded. */ - v = ((quint8) src[bit / 8]) << 8; - if (bit + 5 < nbits) - v += (quint8) src[(bit/8)+1]; - - /* set u to the 5-bit value at the bit'th bit of src. */ - u = (v >> (11 - (bit % 8))) & 0x1F; - dest[i] = BASE32_CHARS[u]; - } - - dest[i] = '\0'; -} - -/* Implements base32 decoding as in rfc3548. Requires that srclen*5 is a multiple of 8. */ -bool base32_decode(char *dest, unsigned destlen, const char *src, unsigned srclen) -{ - unsigned int i, j, bit; - unsigned nbits = srclen * 5; - - /* We need an even multiple of 8 bits, and enough space */ - if ((nbits%8) != 0 || (nbits/8)+1 > destlen) { - Q_ASSERT(false); - return false; - } - - char *tmp = new char[srclen]; - - /* Convert base32 encoded chars to the 5-bit values that they represent. */ - for (j = 0; j < srclen; ++j) - { - if (src[j] > 0x60 && src[j] < 0x7B) - tmp[j] = src[j] - 0x61; - else if (src[j] > 0x31 && src[j] < 0x38) - tmp[j] = src[j] - 0x18; - else if (src[j] > 0x40 && src[j] < 0x5B) - tmp[j] = src[j] - 0x41; - else - { - delete[] tmp; - return false; - } - } - - /* Assemble result byte-wise by applying five possible cases. */ - for (i = 0, bit = 0; bit < nbits; ++i, bit += 8) - { - switch (bit % 40) - { - case 0: - dest[i] = (((quint8)tmp[(bit/5)]) << 3) + (((quint8)tmp[(bit/5)+1]) >> 2); - break; - case 8: - dest[i] = (((quint8)tmp[(bit/5)]) << 6) + (((quint8)tmp[(bit/5)+1]) << 1) - + (((quint8)tmp[(bit/5)+2]) >> 4); - break; - case 16: - dest[i] = (((quint8)tmp[(bit/5)]) << 4) + (((quint8)tmp[(bit/5)+1]) >> 1); - break; - case 24: - dest[i] = (((quint8)tmp[(bit/5)]) << 7) + (((quint8)tmp[(bit/5)+1]) << 2) - + (((quint8)tmp[(bit/5)+2]) >> 3); - break; - case 32: - dest[i] = (((quint8)tmp[(bit/5)]) << 5) + ((quint8)tmp[(bit/5)+1]); - break; - } - } - - delete[] tmp; - return true; -} diff --git a/tests/tests.pri b/tests/tests.pri deleted file mode 100644 index 7ee54c6f..00000000 --- a/tests/tests.pri +++ /dev/null @@ -1,8 +0,0 @@ -TEMPLATE = app -QT += testlib -CONFIG -= app_bundle -CONFIG += testcase - -SRC = ../../src/ -INCLUDEPATH += $${SRC} - diff --git a/tests/tests.pro b/tests/tests.pro deleted file mode 100644 index 31fbc405..00000000 --- a/tests/tests.pro +++ /dev/null @@ -1,2 +0,0 @@ -TEMPLATE = subdirs -SUBDIRS += tst_cryptokey diff --git a/tests/tst_cryptokey/tst_cryptokey.cpp b/tests/tst_cryptokey/tst_cryptokey.cpp deleted file mode 100644 index 1b2f0702..00000000 --- a/tests/tst_cryptokey/tst_cryptokey.cpp +++ /dev/null @@ -1,228 +0,0 @@ -/* Ricochet - https://ricochet.im/ - * Copyright (C) 2014, John Brooks - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * - * * Neither the names of the copyright owners nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include "utils/CryptoKey.h" - -class TestCryptoKey : public QObject -{ - Q_OBJECT - -private slots: - void load(); - void publicKeyDigest(); - void encodedPublicKey(); - void encodedPrivateKey(); - void torServiceID(); - void sign(); -}; - -const char *alice = - "-----BEGIN RSA PRIVATE KEY-----\n" - "MIICXQIBAAKBgQDAS9nLWyK0jWZ8yduqVEhSyZRplTaeUpGWYRi14n1C4sjO6nqm\n" - "ES31UCGDH4nIor2R/XMJCJkJwK+t2XrtiH+jUEHwUGhnMkm3hW5NHt5g39s9YK7l\n" - "xD39O8N2tHUycVq8guhrb1WBQ2/bmZ85nOIuBDZxIuVQZA1U1L6rWGvm+wIDAQAB\n" - "AoGAewYL6JX9thVgpCVga7BQNObSFFpp/xBEJDkqXfLwwIHmhrpsjSIgjPke94yN\n" - "0daMAYJsvjLJ9ftYaZjhlGXngbBJiAU95gcZoTAsn2hNJP22ndGuhi6WEKhYwRxK\n" - "U5d+3Khzy/ysuoay7DSVtpSmpiacWPSiiptEkxNbcbGba8ECQQDeEGoPASmxZoh4\n" - "I2JNQkqSwMKsOZpp/SJhnmLCPoA1oDwlGtu4HF7t9hBXeyIXgLvbfJudFEa+LqR7\n" - "wrKQPn0fAkEA3a7cR7eSRNu1ak7gVfQfnP4tFl3+7UC2hUqVHLA5ks4pLl7/ITa+\n" - "3P04SOs3WpvZJHYJ+hi/anqEPYrD/3B+pQJBAKmjnnHh8IjODDjCxyjAGJntWYoZ\n" - "4yVOtEIgrc830delley+jNUkDzz3+dnqfcu4k0oD8hjYUYaduRe2T5Szt/8CQQDC\n" - "EVt8WUNujp0R9P1FohKu4IFeLGmJD/b5V2KUm927HEpG8xkM3Z1XX0KP64MpCnid\n" - "B80SKeog8CKmsb2F+NiVAkBT1CEAdiFYtf72hnZCLBw5HrqpN+zjw00GjtlrmmNV\n" - "+ILb/YRp5flCY5Se95ExzQqRKzvK5iJg0yEOVF0OcbO+\n" - "-----END RSA PRIVATE KEY-----"; -const char *aliceDigest = "623a1ffc94d8f8edcd5e47fbd45e08deb911d1bc"; -const char *aliceTorID = "mi5b77eu3d4o3tk6"; -const char *aliceSignedTestData = "23fdcd5c7d40b44a7e49619d9048c81931166a0adb80c8981cc8f9a9e02c3923d5fba6d92ea03dc672d009a5fe1be2b582fb935076f880d9aa55511c33620d2aa23336b579dd7ccd1dbf4c845e4100a114d8ac20dd47229e876444f79d5152456a8e26fefa67a12436b3c33728a2ff7cb12250c486f786647574e48bb9208f64"; - -const char *bob = - "-----BEGIN RSA PUBLIC KEY-----\n" - "MIGJAoGBAMP8GyAg/kzwXizpUWjWIMw/lvDffXjsxcq1qmZWZxXJQH/oE8bX+WAf\n" - "VS8iUHVqTykubR0W3QNL6aWSZKBqDQUTN0QBJUF4qdkg3x56C0kwcWa+seDMAvJw\n" - "pcHK9wN7mtWHIhFwhikP//NylrY1MaUxcPjvOKcdJ90k988nnmpZAgMBAAE=\n" - "-----END RSA PUBLIC KEY-----\n"; -const char *bobDigest = "b4780cabdfc3593004431644977cf73bf8475848"; -const char *bobTorID = "wr4azk67ynmtabcd"; - -void TestCryptoKey::load() -{ - CryptoKey key; - QVERIFY(!key.isLoaded()); - - // Private key - QVERIFY(key.loadFromData(alice, CryptoKey::PrivateKey)); - QVERIFY(key.isLoaded()); - QVERIFY(key.isPrivate()); - QCOMPARE(key.bits(), 1024); - key.clear(); - QVERIFY(!key.isLoaded()); - - // Public key - QVERIFY(key.loadFromData(bob, CryptoKey::PublicKey)); - QVERIFY(key.isLoaded()); - QVERIFY(!key.isPrivate()); - QCOMPARE(key.bits(), 1024); - - // DER public key - QByteArray derEncoded = key.encodedPublicKey(CryptoKey::DER); - key.clear(); - QVERIFY(key.loadFromData(derEncoded, CryptoKey::PublicKey, CryptoKey::DER)); - QCOMPARE(key.encodedPublicKey(CryptoKey::DER), derEncoded); - key.clear(); - - // Invalid key - QVERIFY(!key.loadFromData(QByteArray(alice).mid(0, 150), CryptoKey::PrivateKey)); - QVERIFY(!key.isLoaded()); - - // Invalid DER key - QVERIFY(!key.loadFromData(derEncoded.mid(0, derEncoded.size()-2), CryptoKey::PublicKey, CryptoKey::DER)); - QVERIFY(!key.isLoaded()); - - // Empty key - QVERIFY(!key.loadFromData("", CryptoKey::PublicKey)); - QVERIFY(!key.isLoaded()); -} - -void TestCryptoKey::publicKeyDigest() -{ - CryptoKey key; - QVERIFY(key.loadFromData(bob, CryptoKey::PublicKey)); - QCOMPARE(key.publicKeyDigest().toHex(), QByteArray(bobDigest)); - - key.clear(); - QVERIFY(key.loadFromData(alice, CryptoKey::PrivateKey)); - QCOMPARE(key.publicKeyDigest().toHex(), QByteArray(aliceDigest)); -} - -void TestCryptoKey::encodedPublicKey() -{ - CryptoKey key; - QVERIFY(key.loadFromData(bob, CryptoKey::PublicKey)); - - QByteArray pemEncoded = key.encodedPublicKey(CryptoKey::PEM); - QVERIFY(pemEncoded.contains("BEGIN RSA PUBLIC KEY")); - - QByteArray derEncoded = key.encodedPublicKey(CryptoKey::DER); - QCOMPARE(derEncoded.size(), 140); - - CryptoKey key2; - QVERIFY(key2.loadFromData(pemEncoded, CryptoKey::PublicKey)); - QCOMPARE(key.encodedPublicKey(CryptoKey::PEM), key2.encodedPublicKey(CryptoKey::PEM)); - QCOMPARE(key.publicKeyDigest(), key2.publicKeyDigest()); - - CryptoKey key3; - QVERIFY(key3.loadFromData(derEncoded, CryptoKey::PublicKey, CryptoKey::DER)); - QCOMPARE(key.encodedPublicKey(CryptoKey::DER), key3.encodedPublicKey(CryptoKey::DER)); - QCOMPARE(key.publicKeyDigest(), key3.publicKeyDigest()); - - // Doesn't contain a private key - CryptoKey key4; - QVERIFY(!key4.loadFromData(pemEncoded, CryptoKey::PrivateKey)); -} - -void TestCryptoKey::encodedPrivateKey() -{ - CryptoKey key; - QVERIFY(key.loadFromData(alice, CryptoKey::PrivateKey)); - - QByteArray pemEncoded = key.encodedPrivateKey(CryptoKey::PEM); - QVERIFY(pemEncoded.contains("BEGIN RSA PRIVATE KEY")); - - QByteArray derEncoded = key.encodedPrivateKey(CryptoKey::DER); - QVERIFY(!derEncoded.isEmpty()); - - CryptoKey key2; - QVERIFY(key2.loadFromData(pemEncoded, CryptoKey::PrivateKey)); - QCOMPARE(key.encodedPrivateKey(CryptoKey::PEM), key2.encodedPrivateKey(CryptoKey::PEM)); - QCOMPARE(key.publicKeyDigest(), key2.publicKeyDigest()); - - CryptoKey key3; - QVERIFY(key3.loadFromData(derEncoded, CryptoKey::PrivateKey, CryptoKey::DER)); - QCOMPARE(key.encodedPrivateKey(CryptoKey::DER), key3.encodedPrivateKey(CryptoKey::DER)); - QCOMPARE(key.publicKeyDigest(), key3.publicKeyDigest()); -} - -void TestCryptoKey::torServiceID() -{ - CryptoKey key; - QVERIFY(key.loadFromData(bob, CryptoKey::PublicKey)); - - QString id = key.torServiceID(); - QCOMPARE(id.size(), 16); - QCOMPARE(id, QLatin1String(bobTorID)); -} - -void TestCryptoKey::sign() -{ - CryptoKey key; - QVERIFY(key.loadFromData(alice, CryptoKey::PrivateKey)); - - QByteArray data = "test data"; - QByteArray data2 = "different"; - - // Good signature - QByteArray signature = key.signData(data); - QVERIFY(!signature.isEmpty()); - QVERIFY(key.verifyData(data, signature)); - - // Bad signature - QVERIFY(!key.verifyData(data2, signature)); - - // Corrupt signature - QVERIFY(!key.verifyData(data, signature.mid(0, signature.size() - 10))); - - // Wrong public key - CryptoKey key2; - QVERIFY(key2.loadFromData(bob, CryptoKey::PublicKey)); - QVERIFY(!key2.verifyData(data, signature)); - - // Compare to signSHA256 - QByteArray dataDigest = QCryptographicHash::hash(data, QCryptographicHash::Sha256); - QByteArray signature2 = key.signSHA256(dataDigest); - QVERIFY(!signature2.isEmpty()); - // signSHA256 and verifySHA256 - QVERIFY(key.verifySHA256(dataDigest, signature2)); - // signSHA256 and verifyData - QVERIFY(key.verifyData(data, signature2)); - // signData and verifySHA256 - QVERIFY(key.verifySHA256(dataDigest, signature)); - - // Compare to precomputed signature - QByteArray signaturep = QByteArray::fromHex(aliceSignedTestData); - QVERIFY(key.verifyData(data, signaturep)); - QVERIFY(key.verifySHA256(dataDigest, signaturep)); -} - -QTEST_MAIN(TestCryptoKey) -#include "tst_cryptokey.moc" diff --git a/tests/tst_cryptokey/tst_cryptokey.pro b/tests/tst_cryptokey/tst_cryptokey.pro deleted file mode 100644 index 0bdde39d..00000000 --- a/tests/tst_cryptokey/tst_cryptokey.pro +++ /dev/null @@ -1,24 +0,0 @@ -include(../tests.pri) - -SOURCES += tst_cryptokey.cpp \ - $${SRC}/utils/CryptoKey.cpp \ - $${SRC}/utils/SecureRNG.cpp - -unix { - !isEmpty(OPENSSLDIR) { - INCLUDEPATH += $${OPENSSLDIR}/include - LIBS += -L$${OPENSSLDIR}/lib -lcrypto - } else { - CONFIG += link_pkgconfig - PKGCONFIG += libcrypto - } -} -win32 { - isEmpty(OPENSSLDIR):error(You must pass OPENSSLDIR=path/to/openssl to qmake on this platform) - INCLUDEPATH += $${OPENSSLDIR}/include - LIBS += -L$${OPENSSLDIR}/lib -llibeay32 - - # required by openssl - LIBS += -lUser32 -lGdi32 -ladvapi32 -} -macx:LIBS += -lcrypto diff --git a/translation/ricochet_bg.ts b/translation/ricochet_bg.ts deleted file mode 100644 index b11961b6..00000000 --- a/translation/ricochet_bg.ts +++ /dev/null @@ -1,701 +0,0 @@ - - - - - AboutPreferences - - - Ricochet %1 - %1 version, e.g. 1.0.0 - Рикошет %1 - - - - AddContactDialog - - - Share your Ricochet ID to allow connection requests - Сподели своят Рикошет Идентификатор, за да позволиш нови покани - - - - Cancel - Откажи - - - - Add - Добави - - - - ContactActions - - - Open Window - Отвори Прозорец - - - - Details... - Допълнитлно... - - - - Rename - Преименувай - - - - Remove - Премахни - - - - ContactIDField - - - <b>%1</b> is already your contact - <b>%1</b> вече е твой контакт - - - - You can't add yourself as a contact - Не може да добавиш себе си като контакт - - - - Enter an ID starting with <b>ricochet:</b> - Въведи Идентификатор с <b>рикошет:</b> - - - - Copied to clipboard - Копирано - - - - Copy - Копирай - - - - ContactList - - - Online - На линия - - - - Offline - Отписан - - - - Requests - Молби - - - - Rejected - Отхвърлени - - - - Outdated - Стара версия - - - - ContactPreferences - - - Date added: - Дата на добавяне: - - - - Last seen: - Последно видян: - - - - Request: - Покана: - - - - Pending connection - В очакване на връзка - - - - Delivered - Доставено - - - - Accepted - Прието - - - - Error - Грешка - - - - Rejected - Отхвърлено - - - - %1 (Connected) - %1 status, e.g. "Accepted" - %1 (Свързани) - - - - Response: - Отговор: - - - - Rename - Преименувай - - - - Remove - Премахни - - - - ContactRequestDialog - - - Someone new is asking to connect to you - Нов човек те моли да се свържете - - - - Reject - Отхвърли - - - - Accept - Приеми - - - - ContactRequestFields - - - ID: - Индентификатор: - - - - Name: - Име: - - - - Message: - Съобщение: - - - - GeneralPreferences - - - Use a single window for conversations - Използвай само един прозорец за всички разговори - - - - Open links in default browser without prompting - Отваряй връзките в браузъра по подразбиране без да искаш разрешение - - - - Play audio notifications - Използвай звукови известия - - - - Volume - Звук - - - - Language - Език - - - - Restart Ricochet to apply changes - Рестартирай Richchet, за да приложиш промените - - - - LanguagePreferences - - Select Language - Избери език - - - Restart Ricochet to apply changes - Рестартирай Richchet, за да приложиш промените - - - - LanguagesModel - - - System default - Настройки по подразбиране - - - - Main - - - Ricochet Error - Рикошет Грешка - - - - MainToolBar - - - Add Contact - Добави Контакт - - - - Preferences - Настройки - - - - Click to add contacts - Кликни, за да добавиш контакт - - - - MainWindow - - - Remove %1 - Премахни %1 - - - - Do you want to permanently remove %1? - Наистина ли искаш да премахнеш %1 завинаги? - - - - MessageDelegate - - - %1 is offline - %1 nickname - %1 е отписан - - - - Copy ID - Копирай Идентификатора - - - - Copy Link - Копирай Връзката - - - - Open with Browser - Отвори в Браузъра - - - - Add as Contact - Доабви като Контакт - - - - Copy Message - Копирай съобщение - - - - Copy Selection - Копирай селекцията - - - - MessageDialogWrapper - - - Remove %1 - Премахни %1 - - - - Do you want to permanently remove %1? - %1 nickname - Наистина ли искаш да премахнеш %1 завинаги? - - - - This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. - Този контакт повече няма да може да ти пише, и ще бъде уведомен за премахването. Той може да реши да ти прати нова покана за връзка. - - - - NetworkSetupWizard - - - This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. - Интернет връзката на този компютър не е възпрепятствана. Искам да се свържа директно с Тор мрежата. - - - - Connect - Свържи - - - - This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. - Интернет връзката на този компютър е цензурирана, филтрирана или минава през прокси. Трябва да променя настройките на мрежата . - - - - Configure - Настрой - - - - OfflineStateItem - - - Configure - Настрой - - - - Details - Допълнително - - - - Connection failed - Неуспешна връзка - - - - Connecting… - \u2026 is ellipsis - Свързване... - - - - OpenBrowserDialog - - - <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. - <b>Внимание!</b> Отварянето на връзки с браузъра по подразбиране може да прекрати сигурната и анонимността ти.<br><br>Вместо това може да го <a href='.'>копираш</a>. - - - - Don't ask again for links from %1 - Не искай повече връзки от %1 - - - - Don't ask again for any links (not recommended!) - Не искай повече никакви връзки (не се препоръчва!) - - - - Open Browser - Отвори Браузъра - - - - Cancel - Откажи - - - - PreferencesDialog - - - Ricochet Preferences - Настройки на Рикошет - - - - General - Главни - - - Language - Език - - - - Contacts - Контакти - - - - Tor - Тор - - - - About - За Програмта - - - - QCocoaMenuItem - - - Preference - Настройки - - - - StartupStatusPage - - - The Tor process was not started successfully. This is most likely an installation or system error. - Неуспешно стартиране на Тор процесът. Това най-вероятно се дължи на инсталационна или системна грешка. - - - - Quit - Изход - - - - TorBootstrapStatus - - - Connecting to the Tor network… - \u2026 is ellipsis - Свързване с Тор мрежата... - - - - Back - Назад - - - - Hide details - Скрий допълнителната информация - - - - Show details - Покажи допълнителна информация - - - - Done - Готово - - - - TorConfigurationPage - - - Does this computer need a proxy to access the internet? - Този компютър нуждае ли се от прокси, за да се свърже с интернет? - - - - Proxy type: - Вид Прокси: - - - - - None - Никакво - - - - Address: - Адрес: - - - - IP address or hostname - IP адрес или hostname - - - - Port: - Порт: - - - - Username: - Потребителско Име: - - - - - Optional - Незадължително - - - - Password: - Парола: - - - - Does this computer's Internet connection go through a firewall that only allows connections to certain ports? - Минава ли интернет връзката на този компютър през firewall програма, която позволява връзка само през определени портове? - - - - Allowed ports: - Разрешени портове: - - - - Example: 80,443 - Пример: 80,443 - - - - If this computer's Internet connection is censored, you will need to obtain and use bridge relays. - Ако интернет връзката на този компютър е цензурирана, ще се наложи да намериш и използваш bridge relays. - - - - Enter one or more bridge relays (one per line): - Въведи едно или повече bridge relays (по едно на ред): - - - - Back - Назад - - - - Connect - Свържи - - - - TorPreferences - - - Running: - Изпълнява: - - - - - - Yes - Да - - - - - - No - Не - - - - External - Външен - - - - Control connected: - Control connected: - - - - Circuits established: - Circuits established: - - - - Hidden service: - Hidden service: - - - - Online - На Линия - - - - Offline - Отписан - - - - Version: - Версия: - - - - Error: <b>%1</b> - %1 is error message - Грешка: <b>%1</b> - - - - Configure - Настрой - - - - TorStateWidget - - - - Connection failed - Неуспешна връзка - - - - - Connecting… - \u2026 is ellipsis - Свързване... - - - - Connecting… (%1%) - %1 is progress percentage, e.g. 100 - Свързване... (%1%) - - - - Online - На линия - - - - Connected - Свързан - - - diff --git a/translation/ricochet_cs.ts b/translation/ricochet_cs.ts deleted file mode 100644 index ae312177..00000000 --- a/translation/ricochet_cs.ts +++ /dev/null @@ -1,701 +0,0 @@ - - - - - AboutPreferences - - - Ricochet %1 - %1 version, e.g. 1.0.0 - Ricochet %1 - - - - AddContactDialog - - - Share your Ricochet ID to allow connection requests - Sdílejte vaše Richochet ID pro umožnění zádostí připojení - - - - Cancel - Zrušit - - - - Add - Přidat - - - - ContactActions - - - Open Window - Otevřít okno - - - - Details... - Podrobnosti... - - - - Rename - Přejmenovat - - - - Remove - Odstranit - - - - ContactIDField - - - <b>%1</b> is already your contact - <b>%1</b> již je v seznamu kontaktů - - - - You can't add yourself as a contact - Nelze přidat vlastní adresu na seznam kontaktů - - - - Enter an ID starting with <b>ricochet:</b> - Zadejte ID začínající slovem <b>ricochet:</b> - - - - Copied to clipboard - Zkopírováno do schránky - - - - Copy - Zkopírovat - - - - ContactList - - - Online - Online - - - - Offline - Offline - - - - Requests - Žádosti - - - - Rejected - Zamítnuto - - - - Outdated - Zastaralé - - - - ContactPreferences - - - Date added: - Přidáno dne: - - - - Last seen: - Naposledy online: - - - - Request: - Žádost: - - - - Pending connection - Čeká se na připojení - - - - Delivered - Doručeno - - - - Accepted - Přijato - - - - Error - Chyba - - - - Rejected - Zamítnuto - - - - %1 (Connected) - %1 status, e.g. "Accepted" - %1 (Připojeno) - - - - Response: - Odpověď: - - - - Rename - Přejmenovat - - - - Remove - Odstranit - - - - ContactRequestDialog - - - Someone new is asking to connect to you - Někdo nový se s vámi chce spojit - - - - Reject - Zamítnout - - - - Accept - Přijmout - - - - ContactRequestFields - - - ID: - ID: - - - - Name: - Jméno: - - - - Message: - Zpráva: - - - - GeneralPreferences - - - Use a single window for conversations - Používat totéž okno pro všechny konverzace - - - - Open links in default browser without prompting - Otevírat odkazy v defaultním prohlížeči bez dotazování - - - - Play audio notifications - Povolit zvukové upozornění - - - - Volume - Hlasitost - - - - Language - Jazyk - - - - Restart Ricochet to apply changes - Restartovat Ricochet aby se změny projevily - - - - LanguagePreferences - - Select Language - Vybrat jazyk - - - Restart Ricochet to apply changes - Restartovat Ricochet aby se změny projevily - - - - LanguagesModel - - - System default - Systémový - - - - Main - - - Ricochet Error - Chyba programu Ricochet - - - - MainToolBar - - - Add Contact - Přidat kontakt - - - - Preferences - Nastavení - - - - Click to add contacts - Klikněte pro přidání kontaktů - - - - MainWindow - - - Remove %1 - Odstranit %1 - - - - Do you want to permanently remove %1? - Chcete trvale odstranit kontakt %1? - - - - MessageDelegate - - - %1 is offline - %1 nickname - %1 je offline - - - - Copy ID - Kopírovat ID - - - - Copy Link - Kopírovat odkaz - - - - Open with Browser - Otevřít v prohlížeči - - - - Add as Contact - Přidat jako kontakt - - - - Copy Message - Kopírovat zprávu - - - - Copy Selection - Kopírovat výběr - - - - MessageDialogWrapper - - - Remove %1 - Odstranit %1 - - - - Do you want to permanently remove %1? - %1 nickname - Chcete trvale odstranit kontakt %1? - - - - This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. - Tento kontakt vám nebude moci posílat zprávy a bude informován o odstranění ze seznamu kontaktů. Může však zaslat novou žádost o spojení. - - - - NetworkSetupWizard - - - This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. - Tento počítač se připojuje na internet bez jakýchkoli překážek. Chci se připojit přímo na síť Tor. - - - - Connect - Připojit - - - - This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. - Připojení tohoto počítače na internet je cenzurováno, filtrováno nebo vedeno přes proxy. Potřebuji upravit nastavení sítě. - - - - Configure - Nastavit - - - - OfflineStateItem - - - Configure - Nastavit - - - - Details - Podrobnosti - - - - Connection failed - Připojení selhalo - - - - Connecting… - \u2026 is ellipsis - Připojuje se... - - - - OpenBrowserDialog - - - <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. - <b>-Upozornění!</b> Otvírání odkazů v defaultním prohlížeči naruší vaši bezpečnost a anonymitu.<br><br>Můžete místo toho odkaz <a href='.'>zkopírovat do schránky</a>. - - - - Don't ask again for links from %1 - Znovu nepožadovat odkazy od kontaktu %1 - - - - Don't ask again for any links (not recommended!) - Znovu nepožadovat žádné odkazy (nedoporučuje se!) - - - - Open Browser - Otevřít prohlížeč - - - - Cancel - Zrušit - - - - PreferencesDialog - - - Ricochet Preferences - Nastavení programu Ricochet - - - - General - Obecné - - - Language - Jazyk - - - - Contacts - Kontakty - - - - Tor - Tor - - - - About - O programu - - - - QCocoaMenuItem - - - Preference - Nastavení - - - - StartupStatusPage - - - The Tor process was not started successfully. This is most likely an installation or system error. - Proces programu Tor nebyl úspěšně spuštěn. Pravděpodobně se jedná o chybu instalace či systému. - - - - Quit - Ukončit - - - - TorBootstrapStatus - - - Connecting to the Tor network… - \u2026 is ellipsis - Připojuje se k síti Tor… - - - - Back - Zpět - - - - Hide details - Skrýt podrobnosti - - - - Show details - Zobrazit podrobnosti - - - - Done - Hotovo - - - - TorConfigurationPage - - - Does this computer need a proxy to access the internet? - Potřebuje tento počítač pro přístup na internet proxy? - - - - Proxy type: - Typ proxy: - - - - - None - Žádný - - - - Address: - Adresa: - - - - IP address or hostname - IP adresa nebo hostname - - - - Port: - Port: - - - - Username: - Username: - - - - - Optional - Volitelné - - - - Password: - Heslo: - - - - Does this computer's Internet connection go through a firewall that only allows connections to certain ports? - Prochází připojení tohoto počítače na internet přes firewall, jenž umožňuje připojení pouze na určité porty? - - - - Allowed ports: - Povolené porty: - - - - Example: 80,443 - Příklad: 80,443 - - - - If this computer's Internet connection is censored, you will need to obtain and use bridge relays. - Je-li připojení tohoto počítače na internet cenzurováno, potřebujete získat a používat bridge relays. - - - - Enter one or more bridge relays (one per line): - Zadejte jeden či více bridge relays (na jednotlivé řádky): - - - - Back - Zpět - - - - Connect - Připojit - - - - TorPreferences - - - Running: - Spuštěno: - - - - - - Yes - Ano - - - - - - No - Ne - - - - External - Externí - - - - Control connected: - Kontrola připojena: - - - - Circuits established: - Okruhy sestaveny: - - - - Hidden service: - Skrytá služba: - - - - Online - Online - - - - Offline - Offline - - - - Version: - Verze: - - - - Error: <b>%1</b> - %1 is error message - Chyba: <b>%1</b> - - - - Configure - Nastavit - - - - TorStateWidget - - - - Connection failed - Připojení selhalo - - - - - Connecting… - \u2026 is ellipsis - Připojuje se... - - - - Connecting… (%1%) - %1 is progress percentage, e.g. 100 - Připojuje se… (%1%) - - - - Online - Online - - - - Connected - Připojeno - - - diff --git a/translation/ricochet_da.ts b/translation/ricochet_da.ts deleted file mode 100644 index c6c6dc9b..00000000 --- a/translation/ricochet_da.ts +++ /dev/null @@ -1,701 +0,0 @@ - - - - - AboutPreferences - - - Ricochet %1 - %1 version, e.g. 1.0.0 - Ricochet %1 - - - - AddContactDialog - - - Share your Ricochet ID to allow connection requests - Del dit Ricochet ID for at tillade kontaktanmodninger - - - - Cancel - Annuller - - - - Add - Tilføj - - - - ContactActions - - - Open Window - Åbn Vindue - - - - Details... - Detaljer... - - - - Rename - Omdøb - - - - Remove - Fjern - - - - ContactIDField - - - <b>%1</b> is already your contact - <b>%1</b> er allerede på din kontaktliste - - - - You can't add yourself as a contact - Du kan ikke tilføje dig selv som kontaktperson - - - - Enter an ID starting with <b>ricochet:</b> - Skriv et ID der begynder med <b>ricochet:</b> - - - - Copied to clipboard - Kopieret til clipboardet - - - - Copy - Kopiér - - - - ContactList - - - Online - Online - - - - Offline - Offline - - - - Requests - Anmodninger - - - - Rejected - Afvist - - - - Outdated - Forældet - - - - ContactPreferences - - - Date added: - Dato tilføjet: - - - - Last seen: - Sidst set: - - - - Request: - Anmodning: - - - - Pending connection - Afventer forbindelse - - - - Delivered - Afleveret - - - - Accepted - Accepteret - - - - Error - Fejl - - - - Rejected - Afvist - - - - %1 (Connected) - %1 status, e.g. "Accepted" - %1 (Forbundet) - - - - Response: - Svar: - - - - Rename - Omdøb - - - - Remove - Fjern - - - - ContactRequestDialog - - - Someone new is asking to connect to you - Du har en ny kontaktanmodning - - - - Reject - Afvis - - - - Accept - Acceptér - - - - ContactRequestFields - - - ID: - ID: - - - - Name: - Navn: - - - - Message: - Besked: - - - - GeneralPreferences - - - Use a single window for conversations - Hold samtaler i ét vindue - - - - Open links in default browser without prompting - Åbn links i browser uden at spørge - - - - Play audio notifications - Afspil lydnotifikationer - - - - Volume - Lydstyrke - - - - Language - Sprog - - - - Restart Ricochet to apply changes - Genstart Ricochet for at anvende nye indstillinger - - - - LanguagePreferences - - Select Language - Vælg sprog - - - Restart Ricochet to apply changes - Genstart Ricochet for at anvende nye indstillinger - - - - LanguagesModel - - - System default - Standardindstillinger - - - - Main - - - Ricochet Error - Ricochet Fejl - - - - MainToolBar - - - Add Contact - Tilføj Kontaktperson - - - - Preferences - Indstillinger - - - - Click to add contacts - Klik for at tilføje kontaktpersoner - - - - MainWindow - - - Remove %1 - Fjern %1 - - - - Do you want to permanently remove %1? - Vil du fjerne %1 permanent? - - - - MessageDelegate - - - %1 is offline - %1 nickname - %1 er offline - - - - Copy ID - Kopiér ID - - - - Copy Link - Kopiér Link - - - - Open with Browser - Åbn i browser - - - - Add as Contact - Tilføj som kontaktperson - - - - Copy Message - Kopiér besked - - - - Copy Selection - Kopiér markeret tekst - - - - MessageDialogWrapper - - - Remove %1 - Fjern %1 - - - - Do you want to permanently remove %1? - %1 nickname - Vil du fjerne %1 permanent? - - - - This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. - Denne kontaktperson vil ikke længere være i stand til at sende dig beskeder, og vil blive notificeret om fjernelsen. Kontaktpersonen kan vælge at sende dig en ny kontaktanmodning. - - - - NetworkSetupWizard - - - This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. - Denne computers internetadgang er uhindret. Jeg vil gerne forbinde direkte til Tor-netværket. - - - - Connect - Forbind - - - - This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. - Denne computers internetadgang er censureret, filtreret eller bag en proxy. Jeg har brug for at konfigurere netværksindstillinger. - - - - Configure - Konfigurér - - - - OfflineStateItem - - - Configure - Konfigurér - - - - Details - Detaljer - - - - Connection failed - Forbindelsen mislykkedes - - - - Connecting… - \u2026 is ellipsis - Forbinder… - - - - OpenBrowserDialog - - - <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. - <b>Advarsel!</b> Ved at åbne links med din browser kan du forringe din sikkerhed og anonymitet.<br><br>Du kan <a href='.'>kopiere adressen til clipboardet</a> i stedet. - - - - Don't ask again for links from %1 - Spørg ikke igen om links fra %1 - - - - Don't ask again for any links (not recommended!) - Spørg ikke om nogen links (ikke anbefalt!) - - - - Open Browser - Åbn Browser - - - - Cancel - Annullér - - - - PreferencesDialog - - - Ricochet Preferences - Indstillinger for Ricochet - - - - General - Generelt - - - Language - Sprog - - - - Contacts - Kontaktpersoner - - - - Tor - Tor - - - - About - Om - - - - QCocoaMenuItem - - - Preference - Indstilling - - - - StartupStatusPage - - - The Tor process was not started successfully. This is most likely an installation or system error. - Tor-processen kunne ikke starte. Dette er sandsynligvis en installations- eller systemfejl. - - - - Quit - Afslut - - - - TorBootstrapStatus - - - Connecting to the Tor network… - \u2026 is ellipsis - Forbinder til Tor-netværket… - - - - Back - Tilbage - - - - Hide details - Skjul detaljer - - - - Show details - Vis detaljer - - - - Done - Færdig - - - - TorConfigurationPage - - - Does this computer need a proxy to access the internet? - Skal denne computer forbinde gennem en proxy for at tilgå internettet? - - - - Proxy type: - Proxy type: - - - - - None - Ingen - - - - Address: - Adresse: - - - - IP address or hostname - IP-adresse eller værtsnavn - - - - Port: - Port: - - - - Username: - Brugernavn: - - - - - Optional - Valgfrit - - - - Password: - Adgangskode: - - - - Does this computer's Internet connection go through a firewall that only allows connections to certain ports? - Går denne computers internetforbindelse gennem en firewall der kun tillader forbindelser på visse porte? - - - - Allowed ports: - Tilladte porte: - - - - Example: 80,443 - Eksempel: 80,443 - - - - If this computer's Internet connection is censored, you will need to obtain and use bridge relays. - Hvis denne computers internetforbindelse er censureret, er du nødt til at finde og benytte bridge relæer. - - - - Enter one or more bridge relays (one per line): - Indsæt én eller flere bridgerelæer (én per linje): - - - - Back - Tilbage - - - - Connect - Forbind - - - - TorPreferences - - - Running: - Kører: - - - - - - Yes - Ja - - - - - - No - Nej - - - - External - Ekstern - - - - Control connected: - Kontrol forbundet: - - - - Circuits established: - Kredsløb etableret: - - - - Hidden service: - Skjult service: - - - - Online - Online - - - - Offline - Offline - - - - Version: - Version: - - - - Error: <b>%1</b> - %1 is error message - Fejl: <b>%1</b> - - - - Configure - Konfigurér - - - - TorStateWidget - - - - Connection failed - Forbindelse mislykkedes - - - - - Connecting… - \u2026 is ellipsis - Forbinder… - - - - Connecting… (%1%) - %1 is progress percentage, e.g. 100 - Forbinder… (%1) - - - - Online - Online - - - - Connected - Forbundet - - - diff --git a/translation/ricochet_de.ts b/translation/ricochet_de.ts deleted file mode 100644 index 04b3545d..00000000 --- a/translation/ricochet_de.ts +++ /dev/null @@ -1,701 +0,0 @@ - - - - - AboutPreferences - - - Ricochet %1 - %1 version, e.g. 1.0.0 - Ricochet %1 - - - - AddContactDialog - - - Share your Ricochet ID to allow connection requests - Teile deine Ricochet ID, um Verbindungsanfragen zu erlauben - - - - Cancel - Abbrechen - - - - Add - Hinzufügen - - - - ContactActions - - - Open Window - Fenster öffnen - - - - Details... - Details... - - - - Rename - Umbenennen - - - - Remove - Entfernen - - - - ContactIDField - - - <b>%1</b> is already your contact - <b>%1</b> ist bereits dein Kontakt - - - - You can't add yourself as a contact - Du kannst dich nicht selbst als Kontakt hinzufügen - - - - Enter an ID starting with <b>ricochet:</b> - Gebe eine ID an, beginnend mit <b>ricochet:<b> - - - - Copied to clipboard - In die Zwischenablage kopiert - - - - Copy - Kopieren - - - - ContactList - - - Online - Online - - - - Offline - Offline - - - - Requests - Anfragen - - - - Rejected - Abgewiesen - - - - Outdated - veraltet - - - - ContactPreferences - - - Date added: - Hinzugefügt am: - - - - Last seen: - Zuletzt gesehen: - - - - Request: - Anfrage: - - - - Pending connection - Laufende Verbindung - - - - Delivered - Gesendet - - - - Accepted - Akzeptiert - - - - Error - Fehler - - - - Rejected - Abgewiesen - - - - %1 (Connected) - %1 status, e.g. "Accepted" - %1 (Verbunden) - - - - Response: - Antwort: - - - - Rename - Umbenennen - - - - Remove - Entfernen - - - - ContactRequestDialog - - - Someone new is asking to connect to you - Jemand unbekanntes möchte sich zu Ihnen verbinden - - - - Reject - Abweisen - - - - Accept - Annehmen - - - - ContactRequestFields - - - ID: - ID: - - - - Name: - Name: - - - - Message: - Nachricht: - - - - GeneralPreferences - - - Use a single window for conversations - Nutze ein einzelnes Fenster für Unterhaltungen - - - - Open links in default browser without prompting - Öffne Links im Standardbrowser ohne Nachfrage - - - - Play audio notifications - Benachrichtigungston abspielen - - - - Volume - Lautstärke - - - - Language - Sprache - - - - Restart Ricochet to apply changes - Starte Ricochet neu, um Änderungung anzuwenden - - - - LanguagePreferences - - Select Language - Sprache auswählen - - - Restart Ricochet to apply changes - Starte Ricochet neu, um Änderungung anzuwenden - - - - LanguagesModel - - - System default - Systemvorgabe - - - - Main - - - Ricochet Error - Ricochet Fehler - - - - MainToolBar - - - Add Contact - Kontakt hinzufügen - - - - Preferences - Einstellungen - - - - Click to add contacts - Klicken, um Kontakte hinzuzufügen - - - - MainWindow - - - Remove %1 - Entferne %1 - - - - Do you want to permanently remove %1? - Möchten Sie 1% permanent entfernen? - - - - MessageDelegate - - - %1 is offline - %1 nickname - %1 ist offline - - - - Copy ID - Kopiere ID - - - - Copy Link - Kopiere Link - - - - Open with Browser - Im Browser öffnen - - - - Add as Contact - Als Kontakt hinzufügen - - - - Copy Message - Nachricht kopieren - - - - Copy Selection - Auswahl kopieren - - - - MessageDialogWrapper - - - Remove %1 - Entferne %1 - - - - Do you want to permanently remove %1? - %1 nickname - Möchten Sie %1 permanent entfernen? - - - - This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. - Dieser Kontakt wird Ihnen nicht mehr schreiben können, und wird über das Entfernen in Kenntnis gesetzt. Eine neue Verbindungsanfrage kann von ihm/ihr gesendet werden. - - - - NetworkSetupWizard - - - This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. - Die Internetverbindung dieses Rechners ist frei von Hindernissen. Ich möchte gerne eine direkte Verbindung zum Tor-Netzwerk herstellen. - - - - Connect - Verbinden - - - - This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. - Die Internetverbindung dieses Rechners ist zensiert, gefiltert oder vermittelt. - - - - Configure - Konfigurieren - - - - OfflineStateItem - - - Configure - Konfigurieren - - - - Details - Details - - - - Connection failed - Verbindung gescheitert - - - - Connecting… - \u2026 is ellipsis - Verbindet... - - - - OpenBrowserDialog - - - <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. - <b>Warnung!</b> Links mit deinem Standardbrowser zu öffnen kann deine Sicherheit und Anonymität beeinträchtigen.<br> <br>Du kannst stattdessen <a href='.'>in die Zwischenablage kopieren.</a> - - - - Don't ask again for links from %1 - Nicht mehr fragen bei Links von %1 - - - - Don't ask again for any links (not recommended!) - Für keinen Link mehr nachfragen (nicht empfohlen!) - - - - Open Browser - Browser öffnen - - - - Cancel - Abbrechen - - - - PreferencesDialog - - - Ricochet Preferences - Ricochet Einstellungen - - - - General - Allgemein - - - Language - Sprache - - - - Contacts - Kontakte - - - - Tor - Tor - - - - About - Über - - - - QCocoaMenuItem - - - Preference - Einstellung - - - - StartupStatusPage - - - The Tor process was not started successfully. This is most likely an installation or system error. - Der Tor-Prozess konnte nicht gestartet werden. Es handelt sich wahrscheinlich um einen Installations- oder einen Systemfehler. - - - - Quit - Beenden - - - - TorBootstrapStatus - - - Connecting to the Tor network… - \u2026 is ellipsis - Verbinde zum Tor-Netzwerk... - - - - Back - Zurück - - - - Hide details - Details verstecken - - - - Show details - Details anzeigen - - - - Done - Fertig - - - - TorConfigurationPage - - - Does this computer need a proxy to access the internet? - Benötigt dieser Rechner einen Proxy um sich mit dem Internet zu verbinden? - - - - Proxy type: - Proxytyp: - - - - - None - Keiner - - - - Address: - Adresse: - - - - IP address or hostname - IP-Adresse oder Rechnername - - - - Port: - Port: - - - - Username: - Benutzername: - - - - - Optional - Fakultativ - - - - Password: - Passwort: - - - - Does this computer's Internet connection go through a firewall that only allows connections to certain ports? - Geht die Verbindung dieses Rechners durch eine Firewall, die nur Verbindungen zu manchen Ports erlaubt? - - - - Allowed ports: - Erlaubte Ports: - - - - Example: 80,443 - Beispiel: 80, 443 - - - - If this computer's Internet connection is censored, you will need to obtain and use bridge relays. - Sollte die Internetverbindung dieses Rechners zensiert sein, werden Sie Brücken-Relays finden und nutzen müssen. - - - - Enter one or more bridge relays (one per line): - Geben Sie ein oder mehrere Brücken-Relays an (eins pro Zeile): - - - - Back - Zurück - - - - Connect - Verbinden - - - - TorPreferences - - - Running: - Läuft: - - - - - - Yes - Ja - - - - - - No - Nein - - - - External - Extern - - - - Control connected: - Control verbunden: - - - - Circuits established: - Circuits aufgebaut: - - - - Hidden service: - Versteckter Service: - - - - Online - Online - - - - Offline - Offline - - - - Version: - Version: - - - - Error: <b>%1</b> - %1 is error message - Fehler: <b>%1</b> - - - - Configure - Konfigurieren - - - - TorStateWidget - - - - Connection failed - Verbindung gescheitert - - - - - Connecting… - \u2026 is ellipsis - Verbinde... - - - - Connecting… (%1%) - %1 is progress percentage, e.g. 100 - Verbinde... (%1%) - - - - Online - Online - - - - Connected - Verbunden - - - diff --git a/translation/ricochet_en.ts b/translation/ricochet_en.ts deleted file mode 100644 index de044b76..00000000 --- a/translation/ricochet_en.ts +++ /dev/null @@ -1,686 +0,0 @@ - - - - - AboutPreferences - - - Ricochet %1 - %1 version, e.g. 1.0.0 - - - - - AddContactDialog - - - Share your Ricochet ID to allow connection requests - - - - - Cancel - - - - - Add - - - - - ContactActions - - - Open Window - - - - - Details... - - - - - Rename - - - - - Remove - - - - - ContactIDField - - - <b>%1</b> is already your contact - - - - - You can't add yourself as a contact - - - - - Enter an ID starting with <b>ricochet:</b> - - - - - Copied to clipboard - - - - - Copy - - - - - ContactList - - - Online - - - - - Offline - - - - - Requests - - - - - Rejected - - - - - Outdated - - - - - ContactPreferences - - - Date added: - - - - - Last seen: - - - - - Request: - - - - - Pending connection - - - - - Delivered - - - - - Accepted - - - - - Error - - - - - Rejected - - - - - %1 (Connected) - %1 status, e.g. "Accepted" - - - - - Response: - - - - - Rename - - - - - Remove - - - - - ContactRequestDialog - - - Someone new is asking to connect to you - - - - - Reject - - - - - Accept - - - - - ContactRequestFields - - - ID: - - - - - Name: - - - - - Message: - - - - - GeneralPreferences - - - Use a single window for conversations - - - - - Open links in default browser without prompting - - - - - Play audio notifications - - - - - Volume - - - - - Language - - - - - Restart Ricochet to apply changes - - - - - LanguagesModel - - - System default - - - - - Main - - - Ricochet Error - - - - - MainToolBar - - - Add Contact - - - - - Preferences - - - - - Click to add contacts - - - - - MainWindow - - - Remove %1 - - - - - Do you want to permanently remove %1? - - - - - MessageDelegate - - - %1 is offline - %1 nickname - - - - - Copy ID - - - - - Copy Link - - - - - Open with Browser - - - - - Add as Contact - - - - - Copy Message - - - - - Copy Selection - - - - - MessageDialogWrapper - - - Remove %1 - - - - - Do you want to permanently remove %1? - %1 nickname - - - - - This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. - - - - - NetworkSetupWizard - - - This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. - - - - - Connect - - - - - This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. - - - - - Configure - - - - - OfflineStateItem - - - Configure - - - - - Details - - - - - Connection failed - - - - - Connecting… - \u2026 is ellipsis - - - - - OpenBrowserDialog - - - <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. - - - - - Don't ask again for links from %1 - - - - - Don't ask again for any links (not recommended!) - - - - - Open Browser - - - - - Cancel - - - - - PreferencesDialog - - - Ricochet Preferences - - - - - General - - - - - Contacts - - - - - Tor - - - - - About - - - - - QCocoaMenuItem - - - Preference - - - - - StartupStatusPage - - - The Tor process was not started successfully. This is most likely an installation or system error. - - - - - Quit - - - - - TorBootstrapStatus - - - Connecting to the Tor network… - \u2026 is ellipsis - - - - - Back - - - - - Hide details - - - - - Show details - - - - - Done - - - - - TorConfigurationPage - - - Does this computer need a proxy to access the internet? - - - - - Proxy type: - - - - - - None - - - - - Address: - - - - - IP address or hostname - - - - - Port: - - - - - Username: - - - - - - Optional - - - - - Password: - - - - - Does this computer's Internet connection go through a firewall that only allows connections to certain ports? - - - - - Allowed ports: - - - - - Example: 80,443 - - - - - If this computer's Internet connection is censored, you will need to obtain and use bridge relays. - - - - - Enter one or more bridge relays (one per line): - - - - - Back - - - - - Connect - - - - - TorPreferences - - - Running: - - - - - - - Yes - - - - - - - No - - - - - External - - - - - Control connected: - - - - - Circuits established: - - - - - Hidden service: - - - - - Online - - - - - Offline - - - - - Version: - - - - - Error: <b>%1</b> - %1 is error message - - - - - Configure - - - - - TorStateWidget - - - - Connection failed - - - - - - Connecting… - \u2026 is ellipsis - - - - - Connecting… (%1%) - %1 is progress percentage, e.g. 100 - - - - - Online - - - - - Connected - - - - diff --git a/translation/ricochet_es.ts b/translation/ricochet_es.ts deleted file mode 100644 index 32c78296..00000000 --- a/translation/ricochet_es.ts +++ /dev/null @@ -1,701 +0,0 @@ - - - - - AboutPreferences - - - Ricochet %1 - %1 version, e.g. 1.0.0 - Ricochet %1 - - - - AddContactDialog - - - Share your Ricochet ID to allow connection requests - Comparta su ID (identificador) de Ricochet para permitir solicitudes de conexión - - - - Cancel - Cancelar - - - - Add - Añadir - - - - ContactActions - - - Open Window - Abrir Ventana - - - - Details... - Detalles... - - - - Rename - Renombrar - - - - Remove - Eliminar - - - - ContactIDField - - - <b>%1</b> is already your contact - <b>%1</b> ya es su contacto - - - - You can't add yourself as a contact - No puede añadirse a usted mismo como un contacto - - - - Enter an ID starting with <b>ricochet:</b> - Introduzca un ID comience con <b>ricochet:</b> - - - - Copied to clipboard - Copiado al portapapeles - - - - Copy - Copiar - - - - ContactList - - - Online - En línea - - - - Offline - Fuera de línea - - - - Requests - Solicitudes - - - - Rejected - Rechazada - - - - Outdated - Caducada - - - - ContactPreferences - - - Date added: - Añadido desde: - - - - Last seen: - Visto por última vez: - - - - Request: - Solicitud: - - - - Pending connection - Conexión pendiente - - - - Delivered - Entregado - - - - Accepted - Aceptado - - - - Error - Error - - - - Rejected - Rechazado - - - - %1 (Connected) - %1 status, e.g. "Accepted" - %1 (Conectado) - - - - Response: - Respuesta: - - - - Rename - Renombrar - - - - Remove - Eliminar - - - - ContactRequestDialog - - - Someone new is asking to connect to you - Alguien nuevo está pidiendo conectarse con usted - - - - Reject - Rechazar - - - - Accept - Aceptar - - - - ContactRequestFields - - - ID: - ID (identificador): - - - - Name: - Nombre: - - - - Message: - Mensaje: - - - - GeneralPreferences - - - Use a single window for conversations - Usar una única ventana para conversaciones - - - - Open links in default browser without prompting - Abrir enlaces en el navegador predeterminado sin pedir confirmación - - - - Play audio notifications - Reproducir notificaciones de audio - - - - Volume - Volumen - - - - Language - Idioma - - - - Restart Ricochet to apply changes - Reiniciar Ricochet para aplicar los cambios - - - - LanguagePreferences - - Select Language - Seleccionar idioma - - - Restart Ricochet to apply changes - Reiniciar Ricochet para aplicar los cambios - - - - LanguagesModel - - - System default - Predeterminado del sistema - - - - Main - - - Ricochet Error - Error de Ricochet - - - - MainToolBar - - - Add Contact - Añadir Contacto - - - - Preferences - Preferencias - - - - Click to add contacts - Haga clic para añadir contactos - - - - MainWindow - - - Remove %1 - Eliminar %1 - - - - Do you want to permanently remove %1? - ¿Quiere eliminar permanentemente a %1? - - - - MessageDelegate - - - %1 is offline - %1 nickname - %1 está fuera de línea - - - - Copy ID - Copiar ID (identificador) - - - - Copy Link - Copiar Enlace - - - - Open with Browser - Abrir con el Navegador - - - - Add as Contact - Añadir como Contacto - - - - Copy Message - Copiar mensaje - - - - Copy Selection - Copiar selección - - - - MessageDialogWrapper - - - Remove %1 - Eliminar %1 - - - - Do you want to permanently remove %1? - %1 nickname - ¿Quiere eliminar permanentemente a %1? - - - - This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. - Este contacto ya no podrá enviarle mensajes, y será notificado sobre la eliminación. Pueden elegir enviar una nueva solicitud de conexión. - - - - NetworkSetupWizard - - - This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. - La conexión a Internet de este equipo está libre de obstáculos. Me gustaría conectar directamente a la red Tor. - - - - Connect - Conectar - - - - This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. - La conexión a Internet de este equipo está censurada, filtrada, o proxyficada. Necesito configurar las preferencias de red. - - - - Configure - Configurar - - - - OfflineStateItem - - - Configure - Configurar - - - - Details - Detalles - - - - Connection failed - Conexión fallida - - - - Connecting… - \u2026 is ellipsis - Conectando... - - - - OpenBrowserDialog - - - <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. - <b>¡Advertencia!</b> Abrir enlaces con su navegador predeterminado perjudicará su seguridad y anonimato. <br><br>En su lugar puede <a href='.'>copiarlos al portapapeles</a>. - - - - Don't ask again for links from %1 - No volver a preguntar por enlaces desde %1 - - - - Don't ask again for any links (not recommended!) - No volver a preguntar por ningún enlace (¡no recomendado!) - - - - Open Browser - Abrir Navegador - - - - Cancel - Cancelar - - - - PreferencesDialog - - - Ricochet Preferences - Preferencias de Ricochet - - - - General - General - - - Language - Idioma - - - - Contacts - Contactos - - - - Tor - Tor - - - - About - Acerca de - - - - QCocoaMenuItem - - - Preference - Preferencia - - - - StartupStatusPage - - - The Tor process was not started successfully. This is most likely an installation or system error. - El proceso Tor no se inició con éxito. Lo más probable es que esto sea un error de instalación o de sistema. - - - - Quit - Salir - - - - TorBootstrapStatus - - - Connecting to the Tor network… - \u2026 is ellipsis - Conectando a la red Tor... - - - - Back - Atrás - - - - Hide details - Ocultar detalles - - - - Show details - Mostrar detalles - - - - Done - Hecho - - - - TorConfigurationPage - - - Does this computer need a proxy to access the internet? - ¿Este equipo necesita un proxy para acceder a Internet? - - - - Proxy type: - Tipo de proxy: - - - - - None - Ninguno - - - - Address: - Dirección: - - - - IP address or hostname - Dirección IP o nombre del equipo - - - - Port: - Puerto: - - - - Username: - Nombre de usuario: - - - - - Optional - Opcional - - - - Password: - Contraseña: - - - - Does this computer's Internet connection go through a firewall that only allows connections to certain ports? - ¿La conexión a Internet de este equipo va a través de un cortafuegos (firewall) que sólo permite conexiones a ciertos puertos? - - - - Allowed ports: - Puertos permitidos: - - - - Example: 80,443 - Ejemplo: 80,443 - - - - If this computer's Internet connection is censored, you will need to obtain and use bridge relays. - Si la conexión a Internet de este equipo está bajo censura, necesitará obtener y usar repetidores puente (bridge relays). - - - - Enter one or more bridge relays (one per line): - Introduzca uno o más repetidores puente (uno por línea): - - - - Back - Atrás - - - - Connect - Conectar - - - - TorPreferences - - - Running: - Ejecutar: - - - - - - Yes - - - - - - - No - No - - - - External - Externo - - - - Control connected: - Control conectado: - - - - Circuits established: - Circuitos establecidos: - - - - Hidden service: - Servicio oculto: - - - - Online - En línea - - - - Offline - Fuera de línea - - - - Version: - Versión: - - - - Error: <b>%1</b> - %1 is error message - Error: <b>%1</b> - - - - Configure - Configurar - - - - TorStateWidget - - - - Connection failed - Conexión fallida - - - - - Connecting… - \u2026 is ellipsis - Conectando... - - - - Connecting… (%1%) - %1 is progress percentage, e.g. 100 - Conectando... (%1%) - - - - Online - En línea - - - - Connected - Conectado - - - diff --git a/translation/ricochet_et_EE.ts b/translation/ricochet_et_EE.ts deleted file mode 100644 index 92ca7cee..00000000 --- a/translation/ricochet_et_EE.ts +++ /dev/null @@ -1,701 +0,0 @@ - - - - - AboutPreferences - - - Ricochet %1 - %1 version, e.g. 1.0.0 - Ricochet %1 - - - - AddContactDialog - - - Share your Ricochet ID to allow connection requests - Jaga oma Ricochet ID ühenduste jaoks - - - - Cancel - Tühista - - - - Add - Lisa - - - - ContactActions - - - Open Window - Ava aken - - - - Details... - Detailid... - - - - Rename - Nimeta ümber - - - - Remove - Eemalda - - - - ContactIDField - - - <b>%1</b> is already your contact - <b>%1</b> on juba sinu kontaktides - - - - You can't add yourself as a contact - Sa ei saa lisada ennast kontaktiks - - - - Enter an ID starting with <b>ricochet:</b> - Sisesta ID koos <b>ricochet:</b> - - - - Copied to clipboard - Kopeeri lõikeväljale - - - - Copy - Kopeeri - - - - ContactList - - - Online - Võrgus - - - - Offline - Võrgust väljas - - - - Requests - Päringud - - - - Rejected - Tagasi lükatud - - - - Outdated - Vananenud - - - - ContactPreferences - - - Date added: - Lisatud kuupäeval: - - - - Last seen: - Viimati nähtud: - - - - Request: - Päring: - - - - Pending connection - Ootel ühendus - - - - Delivered - Tarnitud - - - - Accepted - Aksepteeritud - - - - Error - Viga - - - - Rejected - Tagasi lükatud - - - - %1 (Connected) - %1 status, e.g. "Accepted" - %1 (Ühendatud) - - - - Response: - Vastus: - - - - Rename - Muuda nime - - - - Remove - Eemalda - - - - ContactRequestDialog - - - Someone new is asking to connect to you - Keegi uus palub sinuga ühenduda - - - - Reject - Lükka tagasi - - - - Accept - Luba - - - - ContactRequestFields - - - ID: - ID: - - - - Name: - Nimi: - - - - Message: - Sõnum: - - - - GeneralPreferences - - - Use a single window for conversations - Kasuta eraldi akent suhtlemiseks - - - - Open links in default browser without prompting - Ava lingid vaikimsi brauseriga ilma märguandeta - - - - Play audio notifications - Mängi heli märguandeid - - - - Volume - Heli valjus - - - - Language - Keel - - - - Restart Ricochet to apply changes - Taaskäivita Ricochet muudatuste jaoks - - - - LanguagePreferences - - Select Language - Keelevalik - - - Restart Ricochet to apply changes - Taaskäivita Ricochet muudatuste jaoks - - - - LanguagesModel - - - System default - Süsteemi keel - - - - Main - - - Ricochet Error - Ricocheti viga - - - - MainToolBar - - - Add Contact - Lisa kontakt - - - - Preferences - Eelistused - - - - Click to add contacts - Kliki kontaktide lisamiseks - - - - MainWindow - - - Remove %1 - Eemalda %1 - - - - Do you want to permanently remove %1? - Kas sa tahad tingimata eemaldada: %1? - - - - MessageDelegate - - - %1 is offline - %1 nickname - %1 on võrgust väljas - - - - Copy ID - Kopeeri ID - - - - Copy Link - Kopeeri link - - - - Open with Browser - Ava koos veebilehitsejaga - - - - Add as Contact - Lisa kontaktina - - - - Copy Message - Kopeeri sõnum - - - - Copy Selection - Kopeeri valik - - - - MessageDialogWrapper - - - Remove %1 - Eemalda %1 - - - - Do you want to permanently remove %1? - %1 nickname - Kas sa tahad tingimata eemaldada %1? - - - - This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. - See kontakt ei saa sulle sõnumeid saata ja on märgitud eemaldamiseks. Kontakt saab saata sulle uue päringu ühendamiseks. - - - - NetworkSetupWizard - - - This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. - Selle arvuti interneti ühendus on vaba takistustest. Tahaksin ühenduda Tor võrku. - - - - Connect - Ühendus - - - - This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. - Selle arvuti interneti ühendus on senseeritud, filtreeritud või proksi all. Vajan seadistamaks võrguühendust. - - - - Configure - Seadistus - - - - OfflineStateItem - - - Configure - Seadistamine - - - - Details - Detailid - - - - Connection failed - Ühenduse viga - - - - Connecting… - \u2026 is ellipsis - Ühendumine... - - - - OpenBrowserDialog - - - <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. - <b>Hoiatus!</b> Lingi avamine sinu veebilehitsejaga võib vähendada sinu turvalisust ja anonüümsust.<br><br>Sa võid hoopis<a href='.'>kopeerida lõikeväljale</a> . - - - - Don't ask again for links from %1 - Ära küsi enam linke %1 - - - - Don't ask again for any links (not recommended!) - Ära küsi rohkem mitte ühegi linkide kohta (ei ole soovitatud!) - - - - Open Browser - Ava Brauser - - - - Cancel - Tühista - - - - PreferencesDialog - - - Ricochet Preferences - Ricochet seadistused - - - - General - Üldine - - - Language - Keel - - - - Contacts - Kontaktid - - - - Tor - Tor - - - - About - Lähemalt - - - - QCocoaMenuItem - - - Preference - Eelistused - - - - StartupStatusPage - - - The Tor process was not started successfully. This is most likely an installation or system error. - Tor protsesside käivitamine ei ole õnnestunud. See on tavaliselt paigaldusel või süsteemiveal. - - - - Quit - Lahku - - - - TorBootstrapStatus - - - Connecting to the Tor network… - \u2026 is ellipsis - Ühendumine Tor võrku... - - - - Back - Tagasi - - - - Hide details - Peida detailid - - - - Show details - Näita detaile - - - - Done - Tehtud - - - - TorConfigurationPage - - - Does this computer need a proxy to access the internet? - Kas see arvuti vajab interneti ühendamiseks proksit? - - - - Proxy type: - Proksi tüüp: - - - - - None - Ei ole - - - - Address: - Aadress: - - - - IP address or hostname - IP aadress või hostinimi - - - - Port: - Port: - - - - Username: - Kasutajanimi: - - - - - Optional - Valikuline - - - - Password: - Parool: - - - - Does this computer's Internet connection go through a firewall that only allows connections to certain ports? - Kas see arvuti vajab interneti ühenduseks läbi tulemüüri ainult teatuid porte? - - - - Allowed ports: - Lubatud pordid: - - - - Example: 80,443 - Näide: 80.443 - - - - If this computer's Internet connection is censored, you will need to obtain and use bridge relays. - Kui selle arvuti internetiühendus on senseeritud, siis vajad ja pead saama kasutada sildühendusi. - - - - Enter one or more bridge relays (one per line): - Sisesta üks või rohkem sildühendusi (üks rea kohta): - - - - Back - Tagasi - - - - Connect - Ühenda - - - - TorPreferences - - - Running: - Jooksutab: - - - - - - Yes - Jah - - - - - - No - Ei - - - - External - Väline - - - - Control connected: - Kontrollitud ühendus: - - - - Circuits established: - Ringühendused kehtestatud: - - - - Hidden service: - Peidetud teenus: - - - - Online - Võrgus - - - - Offline - Võrgust väljas - - - - Version: - Versioon: - - - - Error: <b>%1</b> - %1 is error message - Viga: <b>%1</b> - - - - Configure - Seadistus - - - - TorStateWidget - - - - Connection failed - Ühenduse viga - - - - - Connecting… - \u2026 is ellipsis - Ühendumine... - - - - Connecting… (%1%) - %1 is progress percentage, e.g. 100 - Ühendamine...(%1%) - - - - Online - Võrgus - - - - Connected - Ühendatud - - - diff --git a/translation/ricochet_fi.ts b/translation/ricochet_fi.ts deleted file mode 100644 index 119a47c4..00000000 --- a/translation/ricochet_fi.ts +++ /dev/null @@ -1,701 +0,0 @@ - - - - - AboutPreferences - - - Ricochet %1 - %1 version, e.g. 1.0.0 - Ricochet %1 - - - - AddContactDialog - - - Share your Ricochet ID to allow connection requests - Jaa oma ID kontakteillesi, että he voivat lähettää yhteyspyynnön - - - - Cancel - Peruuta - - - - Add - Lisää - - - - ContactActions - - - Open Window - Avaa ikkuna - - - - Details... - Tiedot... - - - - Rename - Nimeä uudelleen - - - - Remove - Poista - - - - ContactIDField - - - <b>%1</b> is already your contact - <b>%1</b> on jo kontaktisi - - - - You can't add yourself as a contact - Et voi lisätä itseäsi kontaktiksi - - - - Enter an ID starting with <b>ricochet:</b> - Anna ID joka alkaa <b>ricochet:</b> - - - - Copied to clipboard - Kopioitu leikepöydälle - - - - Copy - Kopioi - - - - ContactList - - - Online - Linjoilla - - - - Offline - Ei linjoilla - - - - Requests - Pyynnöt - - - - Rejected - Torjuttu - - - - Outdated - Vanhentunut - - - - ContactPreferences - - - Date added: - Lisätty: - - - - Last seen: - Nähty viimeksi: - - - - Request: - Pyyntö: - - - - Pending connection - Odotetaan yhteyttä - - - - Delivered - Toimitettu - - - - Accepted - Hyväksytty - - - - Error - Virhe - - - - Rejected - Torjuttu - - - - %1 (Connected) - %1 status, e.g. "Accepted" - %1 (Yhdistetty) - - - - Response: - Vastaus: - - - - Rename - Nimeä uudelleen - - - - Remove - Poista - - - - ContactRequestDialog - - - Someone new is asking to connect to you - Sinulle on uusi yhteyspyyntö - - - - Reject - Torju - - - - Accept - Hyväksy - - - - ContactRequestFields - - - ID: - Saajan ID: - - - - Name: - Nimi: - - - - Message: - Viestisi: - - - - GeneralPreferences - - - Use a single window for conversations - Käytä yhtä ikkunaa keskusteluille - - - - Open links in default browser without prompting - Avaa linkit oletusselaimella ilman vahvistusta - - - - Play audio notifications - Soita huomioäänimerkkejä - - - - Volume - Äänenvoimakkuus - - - - Language - Kieli - - - - Restart Ricochet to apply changes - Käynnistä Ricochet uudelleen ottaaksesi tehdyt muutokset käyttöön - - - - LanguagePreferences - - Select Language - Valitse kieli - - - Restart Ricochet to apply changes - Käynnistä Ricochet uudelleen ottaaksesi tehdyt muutokset käyttöön - - - - LanguagesModel - - - System default - Systeemin oletus - - - - Main - - - Ricochet Error - Ricochetin virhe - - - - MainToolBar - - - Add Contact - Lisää kontakti - - - - Preferences - Asetukset - - - - Click to add contacts - Klikkaa lisätäksesi kontakteja - - - - MainWindow - - - Remove %1 - Poista %1 - - - - Do you want to permanently remove %1? - Haluatko poistaa tämän pysyvästi: %1? - - - - MessageDelegate - - - %1 is offline - %1 nickname - %1 ei ole linjoilla - - - - Copy ID - Kopioi ID - - - - Copy Link - Kopioi linkki - - - - Open with Browser - Avaa selaimessa - - - - Add as Contact - Lisää kontaktiksi - - - - Copy Message - Kopioi viesti - - - - Copy Selection - Kopioi valinta - - - - MessageDialogWrapper - - - Remove %1 - Poista %1 - - - - Do you want to permanently remove %1? - %1 nickname - Haluatko poistaa tämän pysyvästi: %1? - - - - This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. - Tämä kontakti ei voi enää viestiä kanssasi ja hän saa tästä tiedon. Kontakti voi halutessaan lähettää sinulle uuden yhteyspyynnön. - - - - NetworkSetupWizard - - - This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. - Tämän tietokoneen internet-yhteydelle ei ole rajoittavia esteitä. Haluan muodostaa suoran yhteyden Tor-verkkoon. - - - - Connect - Yhdistä - - - - This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. - Tämän tietokoneen internet-yhteyttä on sensuroitu tai filtteröity tai yhteys käyttää välityspalvelimia. Minun täytyy määrittää yhteysasetukset. - - - - Configure - Määritä - - - - OfflineStateItem - - - Configure - Määritä - - - - Details - Tiedot - - - - Connection failed - Yhteys epäonnistui - - - - Connecting… - \u2026 is ellipsis - Yhdistetään... - - - - OpenBrowserDialog - - - <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. - <b>Varoitus!</b> Linkkien avaaminen oletusselaimessasi voi vaarantaa tietoturvaasi ja yksityisyyttäsi.<br><br>Voit <a href='.'>kopioida linkin leikepöydälle</a> tämän sijaan. - - - - Don't ask again for links from %1 - Älä kysy uudestaan linkeistä joiden lähteenä on %1 - - - - Don't ask again for any links (not recommended!) - Älä kysy uudestaan mistään linkeistä (Ei suositella!) - - - - Open Browser - Avaa selain - - - - Cancel - Peruuta - - - - PreferencesDialog - - - Ricochet Preferences - Ricochet asetukset - - - - General - Yleistä - - - Language - Kieli - - - - Contacts - Kontaktit - - - - Tor - Tor - - - - About - Tietoa - - - - QCocoaMenuItem - - - Preference - Asetus - - - - StartupStatusPage - - - The Tor process was not started successfully. This is most likely an installation or system error. - Tor-prosessin käynnistys ei onnistunut. Tämä voi johtua asennus- tai järjestelmävirheestä. - - - - Quit - Lopeta - - - - TorBootstrapStatus - - - Connecting to the Tor network… - \u2026 is ellipsis - Yhdistetään Tor-verkkoon... - - - - Back - Palaa - - - - Hide details - Piilota tiedot - - - - Show details - Näytä tiedot - - - - Done - Valmis - - - - TorConfigurationPage - - - Does this computer need a proxy to access the internet? - Tarvitseeko tämä tietokone välityspalvelimen internet-yhteyden luomista varten? - - - - Proxy type: - Välityspalvelimen tyyppi: - - - - - None - Ei mitään - - - - Address: - Osoite: - - - - IP address or hostname - IP-osoite tai isäntä: - - - - Port: - Portti: - - - - Username: - Käyttäjänimi: - - - - - Optional - Vaihtoehtoinen - - - - Password: - Salasana: - - - - Does this computer's Internet connection go through a firewall that only allows connections to certain ports? - Suojaako tämän tietokoneen internet-yhteyttä palomuuri, joka sallii liikenteen vain ennalta määrätyistä porteista? - - - - Allowed ports: - Sallitut portit: - - - - Example: 80,443 - Esimerkiksi: 80,443 - - - - If this computer's Internet connection is censored, you will need to obtain and use bridge relays. - Jos tämän tietokoneen internet-yhteyttä on sensuroitu, sinun täytyy käyttää sillattuja reitittimiä - - - - Enter one or more bridge relays (one per line): - Anna yksi tai useampi reititin (erottele rivinvaihdolla): - - - - Back - Palaa - - - - Connect - Yhdistä - - - - TorPreferences - - - Running: - Käynnissä: - - - - - - Yes - Kyllä - - - - - - No - Ei - - - - External - Ulkoinen - - - - Control connected: - Ohjaus kytketty: - - - - Circuits established: - Alueet perustettu: - - - - Hidden service: - Piilotettu palvelu: - - - - Online - Kyllä - Online - - - - Offline - ei - Offline - - - - Version: - Versio: - - - - Error: <b>%1</b> - %1 is error message - Virhe: <b>%1</b> - - - - Configure - Määritä - - - - TorStateWidget - - - - Connection failed - Yhteys epäonnistui - - - - - Connecting… - \u2026 is ellipsis - Yhdistetään... - - - - Connecting… (%1%) - %1 is progress percentage, e.g. 100 - Yhdistetään… (%1%) - - - - Online - Online - - - - Connected - Yhdistetty - - - diff --git a/translation/ricochet_fil_PH.ts b/translation/ricochet_fil_PH.ts deleted file mode 100644 index 5f7782c2..00000000 --- a/translation/ricochet_fil_PH.ts +++ /dev/null @@ -1,687 +0,0 @@ - - - - - AboutPreferences - - - Ricochet %1 - %1 version, e.g. 1.0.0 - Ricochet %1 - - - - AddContactDialog - - - Share your Ricochet ID to allow connection requests - Ipamahagi ang iyong Ricochet ID para makatanggap ng mga kahilingan ng koneksyon - - - - Cancel - Kanselahin - - - - Add - Magdagdag - - - - ContactActions - - - Open Window - Magbukas ng Window - - - - Details... - Detalye... - - - - Rename - Palitan ang pangalan - - - - Remove - Tanggalin - - - - ContactIDField - - - <b>%1</b> is already your contact - Si <b>%1</b> ay nasa iyong mga contact na. - - - - You can't add yourself as a contact - Hindi mo maaaring idagdag ang iyong sarili bilang isang contact - - - - Enter an ID starting with <b>ricochet:</b> - Magpasok ng isang ID na nagsisimula sa <b>ricochet:</ b> - - - - Copied to clipboard - Kinopya sa clipboard - - - - Copy - Kopyahin - - - - ContactList - - - Online - Online - - - - Offline - Offline - - - - Requests - Mga Kahilingan - - - - Rejected - Tinanggihan - - - - Outdated - - - - - ContactPreferences - - - Date added: - Petsa nang idinagdag: - - - - Last seen: - Huling nakita: - - - - Request: - Hiling: - - - - Pending connection - Naghihintay na mga koneksyon - - - - Delivered - Naihatid - - - - Accepted - Natanggap - - - - Error - Pagkakamali - - - - Rejected - Tinanggihan - - - - %1 (Connected) - %1 status, e.g. "Accepted" - %1 (Nakakonekta) - - - - Response: - Tugon: - - - - Rename - Palitan ang pangalan - - - - Remove - Tanggalin - - - - ContactRequestDialog - - - Someone new is asking to connect to you - May bagong tao na nagtatanong upang kumonekta sa iyo - - - - Reject - Tanggihan - - - - Accept - Tanggapin - - - - ContactRequestFields - - - ID: - ID: - - - - Name: - Pangalan: - - - - Message: - Mensahe: - - - - GeneralPreferences - - - Use a single window for conversations - Gumamit ng solong window para sa mga pag-uusap - - - - Open links in default browser without prompting - Buksan ang mga link sa default na browser nang walang pagdikta - - - - Play audio notifications - - - - - Volume - - - - - Language - - - - - Restart Ricochet to apply changes - - - - - LanguagesModel - - - System default - - - - - Main - - - Ricochet Error - Pagkakamali sa Ricochet - - - - MainToolBar - - - Add Contact - Magdagdag ng Contact - - - - Preferences - Kagustuhan - - - - Click to add contacts - I-click upang magdagdag ng contacts - - - - MainWindow - - - Remove %1 - Tanggalin si %1 - - - - Do you want to permanently remove %1? - Gusto mo bang permanenteng tanggalin si %1? - - - - MessageDelegate - - - %1 is offline - %1 nickname - Si %1 ay offline - - - - Copy ID - Kopyahin ang ID - - - - Copy Link - Kopyahin ang Link - - - - Open with Browser - Buksan gamit ang Browser - - - - Add as Contact - Idagdag bilang Contact - - - - Copy Message - - - - - Copy Selection - - - - - MessageDialogWrapper - - - Remove %1 - Tanggalin si %1 - - - - Do you want to permanently remove %1? - %1 nickname - Gusto mo bang permanenteng tanggalin si %1? - - - - This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. - Ang contact na ito ay hindi na magagawang magpadala ng mensahe sa iyo, siya ay aabisuhan tungkol sa pag-alis. Maaari nilang piliin na magpadala ng bagong kahilingan ng koneksyon. - - - - NetworkSetupWizard - - - This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. - Ang koneksyon sa Internet ng computer na ito ay walang hadlang. Gusto kong direktang kumonekta sa Tor network. - - - - Connect - Ikonekta - - - - This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. - Ang koneksyon sa Internet ng computer na ito ay censored, na-filter, o proxied. Kailangan ko i-configure ang mga setting ng network. - - - - Configure - I-configure - - - - OfflineStateItem - - - Configure - I-configure - - - - Details - Detalye - - - - Connection failed - Nabigo ang koneksyon - - - - Connecting… - \u2026 is ellipsis - Kumukonekta... - - - - OpenBrowserDialog - - - <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. - <b>Babala!</ b> Pagbukas ng mga link gamit ang iyong default na browser ay makakapinsala sa iyong seguridad at anonymity.<br> -<br> Maaari mong <a href='.'>kopyahin sa clipboard</a> sa halip. - - - - Don't ask again for links from %1 - Huwag nang tatanungin ulit para sa mga link mula kay %1 - - - - Don't ask again for any links (not recommended!) - Huwag nang tatanungin ulit para sa anumang mga link (hindi inirerekomenda!) - - - - Open Browser - Buksan ang Browser - - - - Cancel - Kanselahin - - - - PreferencesDialog - - - Ricochet Preferences - Kagustuhan sa Ricochet - - - - General - Pangkalahatan - - - - Contacts - Mga Contact - - - - Tor - Tor - - - - About - Patungkol - - - - QCocoaMenuItem - - - Preference - Kagustuhan - - - - StartupStatusPage - - - The Tor process was not started successfully. This is most likely an installation or system error. - Ang proseso ng Tor ay hindi matagumpay na nakapagsimula. Ito ay pinaka-malamang na pagkakamali sa pag-install o pagkakamali sa sistema. - - - - Quit - Mag-quit - - - - TorBootstrapStatus - - - Connecting to the Tor network… - \u2026 is ellipsis - Kumokonekta sa Tor network... - - - - Back - Bumalik - - - - Hide details - Itago ang mga detalye - - - - Show details - Ipakita ang mga detalye - - - - Done - Tapos na - - - - TorConfigurationPage - - - Does this computer need a proxy to access the internet? - Kailangan ba ng computer na ito ng proxy upang ma-access ang internet? - - - - Proxy type: - Uri ng Proxy: - - - - - None - Wala - - - - Address: - Address: - - - - IP address or hostname - IP address o hostname - - - - Port: - Port: - - - - Username: - Username: - - - - - Optional - Opsyonal - - - - Password: - Password: - - - - Does this computer's Internet connection go through a firewall that only allows connections to certain ports? - Pumupunta ba ang koneksyon sa Internet ng computer na ito sa pamamagitan ng isang firewall na nagbibigay-daan lamang ng mga koneksyon sa ilang mga ports? - - - - Allowed ports: - Pinayagan na ports: - - - - Example: 80,443 - Halimbawa: 80, 443 - - - - If this computer's Internet connection is censored, you will need to obtain and use bridge relays. - Kung ang koneksyon sa Internet ng computer na ito ay censored, kakailanganin mo na makuha at gumamit ng bridge relays. - - - - Enter one or more bridge relays (one per line): - Magpasok ng isa o higit pang mga bridge relays (isa bawat linya): - - - - Back - Bumalik - - - - Connect - Kumonekta - - - - TorPreferences - - - Running: - Tumatakbo: - - - - - - Yes - Oo - - - - - - No - Hindi - - - - External - Panlabas - - - - Control connected: - Naakakonekta ang Control: - - - - Circuits established: - Naitatag ang Circuits: - - - - Hidden service: - Nakatagong serbisyo: - - - - Online - Online - - - - Offline - Offline - - - - Version: - Bersyon: - - - - Error: <b>%1</b> - %1 is error message - Pagkakamali: <b>%1</b> - - - - Configure - I-configure - - - - TorStateWidget - - - - Connection failed - Nabigo ang koneksyon - - - - - Connecting… - \u2026 is ellipsis - Kumukonekta... - - - - Connecting… (%1%) - %1 is progress percentage, e.g. 100 - Kumukonekta… (%1%) - - - - Online - Online - - - - Connected - Konektado - - - diff --git a/translation/ricochet_fr.ts b/translation/ricochet_fr.ts deleted file mode 100644 index 0f33d462..00000000 --- a/translation/ricochet_fr.ts +++ /dev/null @@ -1,701 +0,0 @@ - - - - - AboutPreferences - - - Ricochet %1 - %1 version, e.g. 1.0.0 - Ricochet %1 - - - - AddContactDialog - - - Share your Ricochet ID to allow connection requests - Partagez votre ID Ricochet pour autoriser les demandes de connexion - - - - Cancel - Annuler - - - - Add - Ajouter - - - - ContactActions - - - Open Window - Ouvrir la fenêtre - - - - Details... - Détails... - - - - Rename - Renommer - - - - Remove - Supprimer - - - - ContactIDField - - - <b>%1</b> is already your contact - <b>%1</b> est déjà votre contact - - - - You can't add yourself as a contact - Vous ne pouvez pas vous ajouter vous-même en tant que contact - - - - Enter an ID starting with <b>ricochet:</b> - Entrez un ID commençant par <b>ricochet:</b> - - - - Copied to clipboard - Copié dans le presse-papier - - - - Copy - Copier - - - - ContactList - - - Online - Connecté - - - - Offline - Déconnecté - - - - Requests - Requêtes - - - - Rejected - Rejeté - - - - Outdated - Caduc - - - - ContactPreferences - - - Date added: - Date d'ajout : - - - - Last seen: - Dernière apparition : - - - - Request: - Requête : - - - - Pending connection - En attente de connexion - - - - Delivered - Délivré - - - - Accepted - Accepté - - - - Error - Erreur - - - - Rejected - Rejeté - - - - %1 (Connected) - %1 status, e.g. "Accepted" - %1 (Connecté) - - - - Response: - Réponse : - - - - Rename - Renommer - - - - Remove - Supprimer - - - - ContactRequestDialog - - - Someone new is asking to connect to you - Une nouvelle personne demande à se connecter à vous - - - - Reject - Refuser - - - - Accept - Accepter - - - - ContactRequestFields - - - ID: - ID : - - - - Name: - Nom : - - - - Message: - Message : - - - - GeneralPreferences - - - Use a single window for conversations - Utiliser une seule fenêtre pour les conversations - - - - Open links in default browser without prompting - Ouvrir les hyperliens directement dans le navigateur par défaut sans demander à chaque fois - - - - Play audio notifications - Jouer les notifications sonores - - - - Volume - Volume - - - - Language - Langue - - - - Restart Ricochet to apply changes - Redémarrer l'application pour applique les modifications - - - - LanguagePreferences - - Select Language - Choisir la langue - - - Restart Ricochet to apply changes - Redémarrer l'application pour applique les modifications - - - - LanguagesModel - - - System default - Par défaut du système - - - - Main - - - Ricochet Error - Erreur de Ricochet - - - - MainToolBar - - - Add Contact - Ajouter un contact - - - - Preferences - Préférences - - - - Click to add contacts - Cliquez pour ajouter des contacts - - - - MainWindow - - - Remove %1 - Supprimer %1 - - - - Do you want to permanently remove %1? - Voulez-vous supprimer %1 définitivement ? - - - - MessageDelegate - - - %1 is offline - %1 nickname - %1 est hors-ligne - - - - Copy ID - Copier ID - - - - Copy Link - Copier l'hyperlien - - - - Open with Browser - Ouvrir avec le navigateur - - - - Add as Contact - Ajouter comme contact - - - - Copy Message - Copier le message - - - - Copy Selection - Copier la sélection - - - - MessageDialogWrapper - - - Remove %1 - Supprimer %1 - - - - Do you want to permanently remove %1? - %1 nickname - Voulez-vous supprimer %1 définitivement ? - - - - This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. - Ce contact ne sera plus en mesure de vous envoyer des messages et sera informé du retrait. Il pourra toutefois vous envoyer une nouvelle demande de connexion. - - - - NetworkSetupWizard - - - This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. - La connexion internet de cet ordinateur est libre d'obstacles. Je voudrais me connecter directement au réseau Tor. - - - - Connect - Connexion - - - - This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. - La connexion internet de cet ordinateur est censurée, filtrée, ou derrière un proxy. J'ai besoin de configurer les paramètres réseau. - - - - Configure - Configurer - - - - OfflineStateItem - - - Configure - Configurer - - - - Details - Détails - - - - Connection failed - Connexion échouée - - - - Connecting… - \u2026 is ellipsis - Connexion... - - - - OpenBrowserDialog - - - <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. - <b>Attention !</b> Ouvrir des hyperliens avec votre navigateur par défaut pourrait nuire à votre sécurité et votre anonymat.<br><br>Vous pouvez <a href='.'>copier dans le presse-papier</a> à la place. - - - - Don't ask again for links from %1 - Ne plus demander pour des hyperliens provenant de %1 - - - - Don't ask again for any links (not recommended!) - Ne plus demander à nouveau pour tous les hyperliens (non recommandé) - - - - Open Browser - Ouvrir le navigateur - - - - Cancel - Annuler - - - - PreferencesDialog - - - Ricochet Preferences - Préférences de Ricochet - - - - General - Général - - - Language - Langue - - - - Contacts - Contacts - - - - Tor - Tor - - - - About - À propos - - - - QCocoaMenuItem - - - Preference - Préférences - - - - StartupStatusPage - - - The Tor process was not started successfully. This is most likely an installation or system error. - Le processus Tor n'a pas démarré avec succès. C'est probablement dû à une erreur d'installation ou du système. - - - - Quit - Quitter - - - - TorBootstrapStatus - - - Connecting to the Tor network… - \u2026 is ellipsis - Connexion au réseau Tor... - - - - Back - Retour - - - - Hide details - Cacher les détails - - - - Show details - Afficher les détails - - - - Done - Terminé - - - - TorConfigurationPage - - - Does this computer need a proxy to access the internet? - Est-ce que cet ordinateur à besoin d'un proxy pour accéder à Internet ? - - - - Proxy type: - Type de proxy : - - - - - None - Aucun - - - - Address: - Adresse : - - - - IP address or hostname - Adresse IP ou nom d'hôte - - - - Port: - Port : - - - - Username: - Nom d'utilisateur : - - - - - Optional - En option - - - - Password: - Mot de passe : - - - - Does this computer's Internet connection go through a firewall that only allows connections to certain ports? - Est-ce que la connexion Internet de cet ordinateur passe à travers un pare-feu qui autorise uniquement les connexions à certains ports ? - - - - Allowed ports: - Ports autorisés : - - - - Example: 80,443 - Exemple : 80,443 - - - - If this computer's Internet connection is censored, you will need to obtain and use bridge relays. - Si la connexion Internet de cet ordinateur est censurée, vous allez devoir obtenir et utiliser un pont Tor. - - - - Enter one or more bridge relays (one per line): - Entrez un ou plusieurs ponts Tor (un par ligne) : - - - - Back - Retour - - - - Connect - Connexion - - - - TorPreferences - - - Running: - En cours : - - - - - - Yes - Oui - - - - - - No - Non - - - - External - Externe - - - - Control connected: - Contrôle connecté : - - - - Circuits established: - Circuits établis : - - - - Hidden service: - Service caché : - - - - Online - En ligne - - - - Offline - Hors-ligne - - - - Version: - Version : - - - - Error: <b>%1</b> - %1 is error message - Erreur : <b>%1</b> - - - - Configure - Configurer - - - - TorStateWidget - - - - Connection failed - Connexion échouée - - - - - Connecting… - \u2026 is ellipsis - Connexion... - - - - Connecting… (%1%) - %1 is progress percentage, e.g. 100 - Connexion… (%1%) - - - - Online - En ligne - - - - Connected - Connecté - - - diff --git a/translation/ricochet_he.ts b/translation/ricochet_he.ts deleted file mode 100644 index 379b8b24..00000000 --- a/translation/ricochet_he.ts +++ /dev/null @@ -1,701 +0,0 @@ - - - - - AboutPreferences - - - Ricochet %1 - %1 version, e.g. 1.0.0 - Ricochet %1 - - - - AddContactDialog - - - Share your Ricochet ID to allow connection requests - שתף את ה Ricochet ID שלך על מנת לאפשר חיבורים חדשים - - - - Cancel - בטל - - - - Add - הוסיף - - - - ContactActions - - - Open Window - פתח חלון - - - - Details... - פרטים נוספים... - - - - Rename - שנה שם - - - - Remove - מחק - - - - ContactIDField - - - <b>%1</b> is already your contact - <b>%1</b> הינו איש קשר קיים - - - - You can't add yourself as a contact - אי אפשר להוסיף עצמך לאנשי קשר - - - - Enter an ID starting with <b>ricochet:</b> - הכנס שם משתמש אשר מתחיל ב <b>ricochet:</b> - - - - Copied to clipboard - הועתק ללוח - - - - Copy - העתק - - - - ContactList - - - Online - מחובר - - - - Offline - מנותק - - - - Requests - בקשות - - - - Rejected - קשרים שנדחו - - - - Outdated - אינו בתוקף - - - - ContactPreferences - - - Date added: - הוסף בתאריך - - - - Last seen: - נראה לאחרונה - - - - Request: - בקשה: - - - - Pending connection - חיבורים ממתינים לאישור - - - - Delivered - נשלח - - - - Accepted - התקבל - - - - Error - שגיאה - - - - Rejected - קשרים שנדחו - - - - %1 (Connected) - %1 status, e.g. "Accepted" - %1 (מחובר) - - - - Response: - תגובה: - - - - Rename - שנה שם - - - - Remove - הסר - - - - ContactRequestDialog - - - Someone new is asking to connect to you - התקבלה בקשת תקשורת חדשה - - - - Reject - דחיה - - - - Accept - אשר - - - - ContactRequestFields - - - ID: - זהות: - - - - Name: - שם: - - - - Message: - הודעה: - - - - GeneralPreferences - - - Use a single window for conversations - השתמש בחלון בודד עבור כל השיחות - - - - Open links in default browser without prompting - פתח קישורים בדפדפן ברירת המחדל ללא בקשת אישור - - - - Play audio notifications - השמע התראה קולית - - - - Volume - עוצמה - - - - Language - שפה - - - - Restart Ricochet to apply changes - הפעל מחדש את האפליקציה על מנת לעדכן את ההגדרות החדשות - - - - LanguagePreferences - - Select Language - בחר שפה - - - Restart Ricochet to apply changes - הפעל מחדש את האפליקציה על מנת לעדכן את ההגדרות החדשות - - - - LanguagesModel - - - System default - ברירת מחדל המערכת - - - - Main - - - Ricochet Error - שגיאה ב Ricochet - - - - MainToolBar - - - Add Contact - הוסף איש קשר - - - - Preferences - העדפות - - - - Click to add contacts - הקלק להוספת אנשי קשר - - - - MainWindow - - - Remove %1 - הסר את %1 - - - - Do you want to permanently remove %1? - האם אתה רוצה להסיר לצמיתות את %1 - - - - MessageDelegate - - - %1 is offline - %1 nickname - %1 מנותק - - - - Copy ID - העתק זהות - - - - Copy Link - העתק לינק - - - - Open with Browser - פתח בדפדפן - - - - Add as Contact - הוסיף כאיש קשר - - - - Copy Message - העתק הודעה - - - - Copy Selection - העתק בחירה - - - - MessageDialogWrapper - - - Remove %1 - הסר את %1 - - - - Do you want to permanently remove %1? - %1 nickname - האם אתה רוצה להסיר לצמיתות את %1 - - - - This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. - איש הקשר לא יהיה מסוגל לשלוח לך הודעות, הצד השני יקבל התראה על הסרת הקישור ביניכם. הם יוכלו לשלוח בקשת חיבור מחדש - - - - NetworkSetupWizard - - - This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. - החיבור של המחשב הזה הינו ללא הגבלה כל שהיא כגון רשת משרדית, שרתי פרוקסי, וכדומה ואני מעוניין להתחבר ישירות לרשת Tor. - - - - Connect - התחבר - - - - This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. - החיבור של מחשב זה לאינטרנט הינו מצונזר או עובר דרך פרוקסי ראשי ואני אהיה צריך להגדיר את הגדרות הרשת. - - - - Configure - הגדר - - - - OfflineStateItem - - - Configure - הגדר - - - - Details - פרטים - - - - Connection failed - התחברות נכשלה - - - - Connecting… - \u2026 is ellipsis - מתחבר... - - - - OpenBrowserDialog - - - <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. - <b>זהירות!</b> פתיחת קישורים עם דפדפן הברירת מחדל שלך עלולה לסכן אותך ולחשוף פרטים עלייך!<br><br>תוכל במקום <a href=".">להעתיק את הקישור</a> ללוח. - - - - Don't ask again for links from %1 - אין צורך לבקש אישור פתיחה נוסף מ %1 - - - - Don't ask again for any links (not recommended!) - אין צורך לבקש אישור מאף איש קשר (לא מומלץ ומסוכן!) - - - - Open Browser - פתח דפדפן - - - - Cancel - בטל - - - - PreferencesDialog - - - Ricochet Preferences - הגדרות Ricochet - - - - General - כללי - - - Language - שפה - - - - Contacts - אנשי קשר - - - - Tor - Tor - - - - About - אודות - - - - QCocoaMenuItem - - - Preference - העדפות - - - - StartupStatusPage - - - The Tor process was not started successfully. This is most likely an installation or system error. - תהליך Tor לא הופעל כראוי. ככל הנראה זאת תקלת מערכת או התקנה בעייתית. - - - - Quit - יציאה / התנתק - - - - TorBootstrapStatus - - - Connecting to the Tor network… - \u2026 is ellipsis - מתחבר לרשת Tor - - - - Back - אחורה - - - - Hide details - הסתר פרטים - - - - Show details - הראה פרטים - - - - Done - סיום - - - - TorConfigurationPage - - - Does this computer need a proxy to access the internet? - האם מחשב זה משתמש בפרוקסי על מנת להתחבר לאינטרנט? - - - - Proxy type: - סוג פרוקסי - - - - - None - ללא - - - - Address: - כתובת: - - - - IP address or hostname - כתובת IP או שם מחשב מארח - - - - Port: - פורט: - - - - Username: - שם משתמש: - - - - - Optional - אופציונלי - - - - Password: - סיסמה: - - - - Does this computer's Internet connection go through a firewall that only allows connections to certain ports? - האם חיבור של מחשב זה עובר דרך חומת אש אשר מרשה שימוש בפורטים מסויימים? - - - - Allowed ports: - פורטים מורשים: - - - - Example: 80,443 - דוגמה: 80,443 - - - - If this computer's Internet connection is censored, you will need to obtain and use bridge relays. - במידה וחיבור האינטרנט של מחשב זה מצונזר, יהיה עליך להגדיר שרתי גישור חיבור - - - - Enter one or more bridge relays (one per line): - הכנס גשר חיבור (אחד לשורה): - - - - Back - חזרה - - - - Connect - התחברהתחבר - - - - TorPreferences - - - Running: - פעיל: - - - - - - Yes - כן - - - - - - No - לא - - - - External - חיצוני - - - - Control connected: - בקרה מחוברת: - - - - Circuits established: - מעגלים מחוברים: - - - - Hidden service: - שירות מוסתר: - - - - Online - מחובר - - - - Offline - מנותק - - - - Version: - גירסה: - - - - Error: <b>%1</b> - %1 is error message - שגיאה: <b>%1</b> - - - - Configure - הגדר - - - - TorStateWidget - - - - Connection failed - התחברות נכשלה - - - - - Connecting… - \u2026 is ellipsis - מתחבר... - - - - Connecting… (%1%) - %1 is progress percentage, e.g. 100 - מתחבר... (%1) - - - - Online - מקוון - - - - Connected - מחובר - - - diff --git a/translation/ricochet_it.ts b/translation/ricochet_it.ts deleted file mode 100644 index 9f9a8bf0..00000000 --- a/translation/ricochet_it.ts +++ /dev/null @@ -1,701 +0,0 @@ - - - - - AboutPreferences - - - Ricochet %1 - %1 version, e.g. 1.0.0 - Ricochet %1 - - - - AddContactDialog - - - Share your Ricochet ID to allow connection requests - Condividi il tuo Ricochet ID per permettere le richieste di connessione - - - - Cancel - Annulla - - - - Add - Aggiungere - - - - ContactActions - - - Open Window - Apri Finestra - - - - Details... - Dettagli... - - - - Rename - Rinominare - - - - Remove - Rimuovere - - - - ContactIDField - - - <b>%1</b> is already your contact - <b>%1</b> è già un tuo contatto - - - - You can't add yourself as a contact - Non puoi aggiungere te stesso come contatto - - - - Enter an ID starting with <b>ricochet:</b> - Inserisci un ID che inizi con <b>ricochet:</b> - - - - Copied to clipboard - Copiato negli appunti - - - - Copy - Copia - - - - ContactList - - - Online - Connesso - - - - Offline - Disconnesso - - - - Requests - Richieste - - - - Rejected - Rifiutato - - - - Outdated - - - - - ContactPreferences - - - Date added: - Data di inserimento: - - - - Last seen: - Ultimo accesso: - - - - Request: - Richiesta: - - - - Pending connection - Connessione in sospeso - - - - Delivered - Consegnato - - - - Accepted - Accettato - - - - Error - Errore - - - - Rejected - Rifiutato - - - - %1 (Connected) - %1 status, e.g. "Accepted" - %1 (Collegato) - - - - Response: - Risposta: - - - - Rename - Rinomina - - - - Remove - Rimuovere - - - - ContactRequestDialog - - - Someone new is asking to connect to you - Qualcuno di nuovo sta chiedendo di connettersi a te - - - - Reject - Rifiuta - - - - Accept - Accetta - - - - ContactRequestFields - - - ID: - ID: - - - - Name: - Nome: - - - - Message: - Messaggio: - - - - GeneralPreferences - - - Use a single window for conversations - Usa una finestra singola per le conversazioni - - - - Open links in default browser without prompting - Apri link nel browser di default senza chiedere - - - - Play audio notifications - - - - - Volume - Volume - - - - Language - Lingua - - - - Restart Ricochet to apply changes - Riavvia Ricochet per applicare le modifiche - - - - LanguagePreferences - - Select Language - Selezione lingua - - - Restart Ricochet to apply changes - Riavvia Ricochet per applicare le modifiche - - - - LanguagesModel - - - System default - - - - - Main - - - Ricochet Error - Errore Ricochet - - - - MainToolBar - - - Add Contact - Aggiungere Contatto - - - - Preferences - Preferenze - - - - Click to add contacts - Clicca per aggiungere contatti - - - - MainWindow - - - Remove %1 - Rimuovere %1 - - - - Do you want to permanently remove %1? - Vuoi rimuovere perennemente %1? - - - - MessageDelegate - - - %1 is offline - %1 nickname - %1 è sconnesso - - - - Copy ID - Copia ID - - - - Copy Link - Copia Link - - - - Open with Browser - Apri con il Browser - - - - Add as Contact - Aggiungi come Contatto - - - - Copy Message - Copia messaggio - - - - Copy Selection - Copia selezione - - - - MessageDialogWrapper - - - Remove %1 - Rimuovere %1 - - - - Do you want to permanently remove %1? - %1 nickname - Vuoi rimuovere perennemente %1? - - - - This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. - Questo contatto non sarà più in grado di inviarti messaggi e sarà notificato di questo. Potrebbe decidere di mandare una nuova richiesta. - - - - NetworkSetupWizard - - - This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. - La connessione a internet di questo computer è libera da ostacoli. Vorrei connettermi direttamente alla rete Tor. - - - - Connect - Collegare - - - - This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. - La connessione a internet di questo computer è soggetta a censure, filtri o proxy. Devo configurare le impostazioni del network. - - - - Configure - Configurare - - - - OfflineStateItem - - - Configure - Configurare - - - - Details - Dettagli - - - - Connection failed - Connessione fallita - - - - Connecting… - \u2026 is ellipsis - Collegamento... - - - - OpenBrowserDialog - - - <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. - <b>Attenzione!</b> Aprire link con il tuo browser di default danneggerà la tua sicurezza e anonimia.<br><br>Puoi <a href='.'>copiare negli appunti</a> invece. - - - - Don't ask again for links from %1 - Non chiedere ancora per link da %1 - - - - Don't ask again for any links (not recommended!) - Non chiedere più per ogni link (non raccomandato) - - - - Open Browser - Apri Browser - - - - Cancel - Annulla - - - - PreferencesDialog - - - Ricochet Preferences - Preferenze Ricochet - - - - General - Generali - - - Language - Lingua - - - - Contacts - Contatti - - - - Tor - Tor - - - - About - Riguardo a - - - - QCocoaMenuItem - - - Preference - Preferenza - - - - StartupStatusPage - - - The Tor process was not started successfully. This is most likely an installation or system error. - Tor non è stato avviato correttamente. Probabilmente si tratta di un errore di sistema o di installazione. - - - - Quit - Esci - - - - TorBootstrapStatus - - - Connecting to the Tor network… - \u2026 is ellipsis - Collegamento al network Tor... - - - - Back - Indietro - - - - Hide details - Nascondi dettagli - - - - Show details - Mostra dettagli - - - - Done - Fatto - - - - TorConfigurationPage - - - Does this computer need a proxy to access the internet? - Questo computer necessita di un proxy per accedere a internet? - - - - Proxy type: - Tipo di proxy: - - - - - None - Nessuno - - - - Address: - Indirizzo: - - - - IP address or hostname - Indirizzo IP o nome host - - - - Port: - Porta: - - - - Username: - Nome utente: - - - - - Optional - Opzionale - - - - Password: - Password: - - - - Does this computer's Internet connection go through a firewall that only allows connections to certain ports? - La connessione di questo computer passa attraverso un firewall che permette la connessione solo ad alcune porte? - - - - Allowed ports: - Porte permesse: - - - - Example: 80,443 - Esempio: 80,443 - - - - If this computer's Internet connection is censored, you will need to obtain and use bridge relays. - Se la connessione a internet di questo computer è censurata dovrai ottenere ed usare dei bridge relay - - - - Enter one or more bridge relays (one per line): - Inserisci uno o più bridge relay (uno per linea): - - - - Back - Indietro - - - - Connect - Collegare - - - - TorPreferences - - - Running: - In esecuzione: - - - - - - Yes - Si - - - - - - No - No - - - - External - Esterno - - - - Control connected: - Controllo connesso: - - - - Circuits established: - Circuiti stabiliti: - - - - Hidden service: - Hidden service: - - - - Online - Connesso - - - - Offline - Disconnesso - - - - Version: - Versione: - - - - Error: <b>%1</b> - %1 is error message - Errore: <b>%1</b> - - - - Configure - Configurare - - - - TorStateWidget - - - - Connection failed - Connessione fallita - - - - - Connecting… - \u2026 is ellipsis - Collegamento... - - - - Connecting… (%1%) - %1 is progress percentage, e.g. 100 - Collegamento... (%1%) - - - - Online - Connesso - - - - Connected - Connesso - - - diff --git a/translation/ricochet_it_IT.ts b/translation/ricochet_it_IT.ts deleted file mode 100644 index 7d6a3a06..00000000 --- a/translation/ricochet_it_IT.ts +++ /dev/null @@ -1,701 +0,0 @@ - - - - - AboutPreferences - - - Ricochet %1 - %1 version, e.g. 1.0.0 - Ricochet %1 - - - - AddContactDialog - - - Share your Ricochet ID to allow connection requests - Condividi il tuo Ricochet ID per permettere agli altri utenti di inviarti richieste di connessione - - - - Cancel - Annulla - - - - Add - Aggiungi - - - - ContactActions - - - Open Window - Apri finestra - - - - Details... - Dettagli... - - - - Rename - Rinomina - - - - Remove - Rimuovi - - - - ContactIDField - - - <b>%1</b> is already your contact - <b>%1</b> è già tra i tuoi contatti - - - - You can't add yourself as a contact - Non puoi aggiungere te stesso ai tuoi contatti - - - - Enter an ID starting with <b>ricochet:</b> - Inserisci un ID che inizi con <b>ricochet:</b> - - - - Copied to clipboard - Copiato negli appunti - - - - Copy - Copia - - - - ContactList - - - Online - Connesso - - - - Offline - Disconnesso - - - - Requests - Richieste - - - - Rejected - Rifiutato - - - - Outdated - Non aggiornato - - - - ContactPreferences - - - Date added: - Aggiunto il: - - - - Last seen: - Ultimo accesso: - - - - Request: - Richiesta: - - - - Pending connection - Connessione in attesa - - - - Delivered - Consegnato - - - - Accepted - Accettato - - - - Error - Errore - - - - Rejected - Rifiutato - - - - %1 (Connected) - %1 status, e.g. "Accepted" - %1 (In linea) - - - - Response: - Risposta: - - - - Rename - Rinomina - - - - Remove - Rimuovi - - - - ContactRequestDialog - - - Someone new is asking to connect to you - Un nuovo utente vuole connettersi con te - - - - Reject - Rifiuta - - - - Accept - Accetta - - - - ContactRequestFields - - - ID: - ID: - - - - Name: - Nome: - - - - Message: - Messaggio: - - - - GeneralPreferences - - - Use a single window for conversations - Usa una finestra singola per le conversazioni - - - - Open links in default browser without prompting - Apri i link nel browser di default senza chiedere - - - - Play audio notifications - Riproduci suono notifiche - - - - Volume - Volume - - - - Language - Lingua - - - - Restart Ricochet to apply changes - Riavvia Ricochet per applicare le modifiche - - - - LanguagePreferences - - Select Language - Seleziona lingua - - - Restart Ricochet to apply changes - Riavvia Ricochet per applicare le modifiche - - - - LanguagesModel - - - System default - Impostazioni di fabbrica - - - - Main - - - Ricochet Error - Errore Ricochet - - - - MainToolBar - - - Add Contact - Aggiungi contatto - - - - Preferences - Preferenze - - - - Click to add contacts - Clicca per aggiungere contatti - - - - MainWindow - - - Remove %1 - Rimuovi %1 - - - - Do you want to permanently remove %1? - Vuoi rimuovere definitivamente %1? - - - - MessageDelegate - - - %1 is offline - %1 nickname - %1 non è in linea - - - - Copy ID - Copia ID - - - - Copy Link - Copia link - - - - Open with Browser - Apri nel il browser - - - - Add as Contact - Aggiungi ai tuoi contatti - - - - Copy Message - Copia messaggio - - - - Copy Selection - Copia selezione - - - - MessageDialogWrapper - - - Remove %1 - Rimuovi %1 - - - - Do you want to permanently remove %1? - %1 nickname - Vuoi rimuovere %1 definitivamente? - - - - This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. - Questo contatto non potrà più inviarti messaggi. Riceverà un avviso per informarlo di questo. Potrebbe comunque inviarti una nuova richiesta di inserimento fra i contatti. - - - - NetworkSetupWizard - - - This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. - La connessione a Internet di questo computer è libera da ostacoli. Voglio connettermi direttamente alla rete Tor. - - - - Connect - Connetti - - - - This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. - La connessione a Internet di questo computer è soggetta a censure, filtri o proxy. Devo configurare le impostazioni di rete. - - - - Configure - Configura - - - - OfflineStateItem - - - Configure - Configura - - - - Details - Dettagli - - - - Connection failed - Connessione fallita - - - - Connecting… - \u2026 is ellipsis - Connessione in corso... - - - - OpenBrowserDialog - - - <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. - <b>Attenzione!</b> Aprire un link nel browser di default metterà a repentaglio la tua sicurezza e anonimia.<br><br>Puoi invece <a href='.'>copiarlo negli appunti</a>. - - - - Don't ask again for links from %1 - Non chiedere più per i link inviati da %1 - - - - Don't ask again for any links (not recommended!) - Non chiedere più per ogni link (scelta non consigliata) - - - - Open Browser - Apri browser - - - - Cancel - Annulla - - - - PreferencesDialog - - - Ricochet Preferences - Preferenze Ricochet - - - - General - Generale - - - Language - Lingua - - - - Contacts - Contatti - - - - Tor - Tor - - - - About - Info - - - - QCocoaMenuItem - - - Preference - Preferenza - - - - StartupStatusPage - - - The Tor process was not started successfully. This is most likely an installation or system error. - Tor non si è avviato correttamente. Potrebbe trattarsi di un errore di sistema o di installazione. - - - - Quit - Esci - - - - TorBootstrapStatus - - - Connecting to the Tor network… - \u2026 is ellipsis - Collegamento alla rete Tor... - - - - Back - Indietro - - - - Hide details - Nascondi dettagli - - - - Show details - Mostra dettagli - - - - Done - Fatto - - - - TorConfigurationPage - - - Does this computer need a proxy to access the internet? - Questo computer necessita di un proxy per accedere a internet? - - - - Proxy type: - Tipo di proxy: - - - - - None - Nessuno - - - - Address: - Indirizzo: - - - - IP address or hostname - Indirizzo IP o nome host - - - - Port: - Porta: - - - - Username: - Nome utente: - - - - - Optional - Facoltativo - - - - Password: - Password: - - - - Does this computer's Internet connection go through a firewall that only allows connections to certain ports? - La connessione ad Internet di questo computer passa attraverso un firewall che consente la connessione solo su determinate porte? - - - - Allowed ports: - Porte consentite: - - - - Example: 80,443 - Esempio: 80,443 - - - - If this computer's Internet connection is censored, you will need to obtain and use bridge relays. - Se la connessione a Internet di questo computer è censurata dovrai ottenere ed usare dei bridge relay - - - - Enter one or more bridge relays (one per line): - Aggiungi uno o più bridge relay (uno per linea): - - - - Back - Indietro - - - - Connect - Connetti - - - - TorPreferences - - - Running: - In esecuzione: - - - - - - Yes - Si - - - - - - No - No - - - - External - Esterno - - - - Control connected: - Control connesso: - - - - Circuits established: - Circuiti stabiliti: - - - - Hidden service: - Hidden service: - - - - Online - Connesso - - - - Offline - Disconnesso - - - - Version: - Versione: - - - - Error: <b>%1</b> - %1 is error message - Errore: <b>%1</b> - - - - Configure - Configura - - - - TorStateWidget - - - - Connection failed - Connessione fallita - - - - - Connecting… - \u2026 is ellipsis - Connessione in corso... - - - - Connecting… (%1%) - %1 is progress percentage, e.g. 100 - Connessione in corso... (%1%) - - - - Online - Connesso - - - - Connected - Connesso - - - diff --git a/translation/ricochet_ja.ts b/translation/ricochet_ja.ts deleted file mode 100644 index cc645b65..00000000 --- a/translation/ricochet_ja.ts +++ /dev/null @@ -1,701 +0,0 @@ - - - - - AboutPreferences - - - Ricochet %1 - %1 version, e.g. 1.0.0 - リコシェイ %1 - - - - AddContactDialog - - - Share your Ricochet ID to allow connection requests - 接続リクエストを許可するためにリコシェイ IDをシェアしてください - - - - Cancel - 取り消す - - - - Add - 追加する - - - - ContactActions - - - Open Window - ウィンドウを開く - - - - Details... - 詳細 - - - - Rename - 名前を変更する - - - - Remove - 削除する - - - - ContactIDField - - - <b>%1</b> is already your contact - <b>%1</b> は既にあなたのコンタクトに存在します - - - - You can't add yourself as a contact - 自分自身をコンタクトに追加することはできません - - - - Enter an ID starting with <b>ricochet:</b> - <b>リコシェイ</b> から始まるID を入力してください - - - - Copied to clipboard - クリップボードにコピーしました - - - - Copy - コピー - - - - ContactList - - - Online - オンライン - - - - Offline - オフライン - - - - Requests - リクエスト - - - - Rejected - 拒否されました - - - - Outdated - 古い - - - - ContactPreferences - - - Date added: - 追加された日 - - - - Last seen: - 最後に閲覧 - - - - Request: - リクエスト - - - - Pending connection - 接続を保留中です - - - - Delivered - 送信完了 - - - - Accepted - 受諾されました - - - - Error - エラー - - - - Rejected - 拒否されました - - - - %1 (Connected) - %1 status, e.g. "Accepted" - %1(接続されました) - - - - Response: - 返答 - - - - Rename - 名前を変更する - - - - Remove - 削除する - - - - ContactRequestDialog - - - Someone new is asking to connect to you - あなたとつながりたい人がいます - - - - Reject - 拒否する - - - - Accept - 受諾する - - - - ContactRequestFields - - - ID: - ID - - - - Name: - 名前 - - - - Message: - メッセージ - - - - GeneralPreferences - - - Use a single window for conversations - 一つのウィンドウで会話する - - - - Open links in default browser without prompting - ブラウザでウェブリンクを開く際には都度通知せず開く - - - - Play audio notifications - 通知音を鳴らす - - - - Volume - 音量 - - - - Language - 言語 - - - - Restart Ricochet to apply changes - 変更を反映するためリコシェイを再起動する - - - - LanguagePreferences - - Select Language - 言語を選択 - - - Restart Ricochet to apply changes - 変更を反映するためリコシェイを再起動する - - - - LanguagesModel - - - System default - システムデフォルト - - - - Main - - - Ricochet Error - リコシェイのエラー - - - - MainToolBar - - - Add Contact - コンタクトを追加する - - - - Preferences - 選択 - - - - Click to add contacts - クリックしてコンタクトを追加する - - - - MainWindow - - - Remove %1 - %1を削除する - - - - Do you want to permanently remove %1? - %1を永久に削除しますか? - - - - MessageDelegate - - - %1 is offline - %1 nickname - %1はオフラインです - - - - Copy ID - IDをコピーする - - - - Copy Link - リンクをコピーする - - - - Open with Browser - ブラウザで開く - - - - Add as Contact - コンタクトに追加する - - - - Copy Message - メッセージをコピーする - - - - Copy Selection - 選択範囲をコピーする - - - - MessageDialogWrapper - - - Remove %1 - %1を削除する - - - - Do you want to permanently remove %1? - %1 nickname - %1を永久に削除しますか? - - - - This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. - このコンタクトはあなたにメッセージを送ることはできなくなり、削除されたことが通知されます。新たにつながりリクエストを送ることを選択するかもしれません。 - - - - NetworkSetupWizard - - - This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. - このコンピュータはインターネット接続に問題がありません。トールネットワークに直接接続します - - - - Connect - 接続する - - - - This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. - このコンピュータのインターネット接続は検閲、フィルタ、プロキシ設定がされています。ネットワークコンフィギュレーションの設定を変更する必要があります。 - - - - Configure - 設定する - - - - OfflineStateItem - - - Configure - 設定する - - - - Details - 詳細 - - - - Connection failed - 接続に失敗しました - - - - Connecting… - \u2026 is ellipsis - 接続中 - - - - OpenBrowserDialog - - - <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. - <b> 警告!<b/> このリンクを既定のブラウザで開くとあなたのセキュリティと匿名性に害を及ぼす可能性があります。<br><br> 代わりに<a href=','> クリップボードにコピーする</a> こともできます。 - - - - Don't ask again for links from %1 - %1からのリンクはいつも許可する - - - - Don't ask again for any links (not recommended!) - %1からのすべてのリンクを再確認しない(非推奨) - - - - Open Browser - ブラウザを開く - - - - Cancel - 取り消す - - - - PreferencesDialog - - - Ricochet Preferences - リコシェイの設定 - - - - General - 一般 - - - Language - 言語 - - - - Contacts - コンタクト - - - - Tor - トール - - - - About - 情報 - - - - QCocoaMenuItem - - - Preference - 設定 - - - - StartupStatusPage - - - The Tor process was not started successfully. This is most likely an installation or system error. - トールの処理が適切に開始されませんでした。インストールエラーか、システムエラーの可能性が高いです。 - - - - Quit - 終了する - - - - TorBootstrapStatus - - - Connecting to the Tor network… - \u2026 is ellipsis - トールネットワークに接続中 - - - - Back - ドル - - - - Hide details - 詳細を隠す - - - - Show details - 詳細を開く - - - - Done - 完了 - - - - TorConfigurationPage - - - Does this computer need a proxy to access the internet? - このコンピュータはインターネットにアクセスするのにプロキシが必要ですか? - - - - Proxy type: - プロキシの種類 - - - - - None - 無い - - - - Address: - アドレス - - - - IP address or hostname - IPアドレスかホストネーム - - - - Port: - ポート - - - - Username: - ユーザーネーム - - - - - Optional - 必須では無い - - - - Password: - パスワード - - - - Does this computer's Internet connection go through a firewall that only allows connections to certain ports? - このコンピュータは特定のポートの身を許可するファイアウォールを経由しますか? - - - - Allowed ports: - 許可されたポート - - - - Example: 80,443 - 例えば:80 、443 - - - - If this computer's Internet connection is censored, you will need to obtain and use bridge relays. - このコンピュータのインターネット接続が検知された場合、ブリッジリレーを手に入れて利用する必要があります - - - - Enter one or more bridge relays (one per line): - 1つもしくはそれ以上のブリッジリレーを入力してください(1ラインにつき1つ): - - - - Back - 戻る - - - - Connect - 接続する - - - - TorPreferences - - - Running: - 実行中: - - - - - - Yes - はい - - - - - - No - いいえ - - - - External - 外部の - - - - Control connected: - 制御された接続 - - - - Circuits established: - 回線の成立 - - - - Hidden service: - 秘匿サービス - - - - Online - オンライン - - - - Offline - オフライン - - - - Version: - バージョン - - - - Error: <b>%1</b> - %1 is error message - エラー:<b> %1</b> - - - - Configure - 設定 - - - - TorStateWidget - - - - Connection failed - 接続に失敗しました - - - - - Connecting… - \u2026 is ellipsis - 接続中 - - - - Connecting… (%1%) - %1 is progress percentage, e.g. 100 - 接続中(%1%) - - - - Online - オンライン - - - - Connected - 接続済 - - - diff --git a/translation/ricochet_nb.ts b/translation/ricochet_nb.ts deleted file mode 100644 index eb299492..00000000 --- a/translation/ricochet_nb.ts +++ /dev/null @@ -1,701 +0,0 @@ - - - - - AboutPreferences - - - Ricochet %1 - %1 version, e.g. 1.0.0 - Ricochet %1 - - - - AddContactDialog - - - Share your Ricochet ID to allow connection requests - Del din Ricochet-ID for å tillate tilkoblingsforespørsler - - - - Cancel - Avbryt - - - - Add - Legg til - - - - ContactActions - - - Open Window - Åpne vindu - - - - Details... - Detaljer... - - - - Rename - Endre navn - - - - Remove - Fjern - - - - ContactIDField - - - <b>%1</b> is already your contact - <b>%1</b> er allerede din kontakt - - - - You can't add yourself as a contact - Du kan ikke legge til deg selv som en kontakt - - - - Enter an ID starting with <b>ricochet:</b> - Skriv inn en ID som starter med <b>ricochet:</b> - - - - Copied to clipboard - Kopiert til utklippstavlen - - - - Copy - Kopier - - - - ContactList - - - Online - Tilkoblet - - - - Offline - Frakoblet - - - - Requests - Forespørsler - - - - Rejected - Avvist - - - - Outdated - Utdatert - - - - ContactPreferences - - - Date added: - Dato lagt til: - - - - Last seen: - Sist sett: - - - - Request: - Forespørsel: - - - - Pending connection - I påvente av forbindelse - - - - Delivered - Levert - - - - Accepted - Godtatt - - - - Error - Feil - - - - Rejected - Avvist - - - - %1 (Connected) - %1 status, e.g. "Accepted" - %1 (tilkoblet) - - - - Response: - Respons: - - - - Rename - Endre navn - - - - Remove - Fjern - - - - ContactRequestDialog - - - Someone new is asking to connect to you - Noen nye spør om de kan å få koble til - - - - Reject - Avvist - - - - Accept - Godta - - - - ContactRequestFields - - - ID: - ID: - - - - Name: - Navn: - - - - Message: - Melding: - - - - GeneralPreferences - - - Use a single window for conversations - Bruk ètt vindu for samtaler - - - - Open links in default browser without prompting - Åpne lenker i standardnettleser uten å spørre - - - - Play audio notifications - Spill av lydvarsler - - - - Volume - Volum - - - - Language - Språk - - - - Restart Ricochet to apply changes - Ta en omstart av Ricochet for å bruke endringene - - - - LanguagePreferences - - Select Language - Velg språk - - - Restart Ricochet to apply changes - Ta en omstart av Ricochet for å bruke endringene - - - - LanguagesModel - - - System default - Systemstandard - - - - Main - - - Ricochet Error - Ricochet-feil - - - - MainToolBar - - - Add Contact - Legg til kontakt - - - - Preferences - Preferanser - - - - Click to add contacts - Trykk for å legge til kontakter - - - - MainWindow - - - Remove %1 - Fjern %1 - - - - Do you want to permanently remove %1? - Ønsker du å slette %1 for alltid? - - - - MessageDelegate - - - %1 is offline - %1 nickname - %1 er frakoblet - - - - Copy ID - Kopier ID - - - - Copy Link - Kopier lenke - - - - Open with Browser - Åpne i nettleser - - - - Add as Contact - Legg til som kontakt - - - - Copy Message - Kopier melding - - - - Copy Selection - Kopier utvalg - - - - MessageDialogWrapper - - - Remove %1 - Fjern %1 - - - - Do you want to permanently remove %1? - %1 nickname - Ønsker du å permanent fjerne %1? - - - - This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. - Denne kontakten vil ikke lengre være i stand til å kunne kontakte deg og er blitt varslet om dette. Vedkommende kan velge å sende ny tilkoblingsforespørsel. - - - - NetworkSetupWizard - - - This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. - Denne datamaskinens internettforbindelse er fri for hindre. Jeg ønsker å koble direkte til Tor-nettverket. - - - - Connect - Koble til - - - - This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. - Denne datamaskinens internettforbindelse er sensurert, filtrert, eller bak proxy. Jeg trenger å konfigurere nettverksinnstillinger. - - - - Configure - Konfigurer - - - - OfflineStateItem - - - Configure - Konfigurer - - - - Details - Detaljer - - - - Connection failed - Tilkoblingsfeil - - - - Connecting… - \u2026 is ellipsis - Kobler til... - - - - OpenBrowserDialog - - - <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. - <b>Advarsel!</b> Åpning av lenker i din standardnettleser vil svekke sikkerheten og minske anonymiteten din.<br><br>Du kan <a href='.'>kopiere til utklippstavlen</a> i stedet for. - - - - Don't ask again for links from %1 - Ikke spør igjen for lenker fra %1 - - - - Don't ask again for any links (not recommended!) - Ikke spør igjen for noen lenker som helst (ikke anbefalt!) - - - - Open Browser - Åpne nettleser - - - - Cancel - Avbryt - - - - PreferencesDialog - - - Ricochet Preferences - Ricochet-preferanser - - - - General - Generelt - - - Language - Språk - - - - Contacts - Kontakter - - - - Tor - Tor - - - - About - Om - - - - QCocoaMenuItem - - - Preference - Preferanse - - - - StartupStatusPage - - - The Tor process was not started successfully. This is most likely an installation or system error. - Tor-prosessen ble ikke startet vellykket. Dette er mest sannsynlig en installasjons- eller systemfeil. - - - - Quit - Avslutt - - - - TorBootstrapStatus - - - Connecting to the Tor network… - \u2026 is ellipsis - Kobler til Tor-nettverket - - - - Back - Tilbake - - - - Hide details - Gjem detaljer - - - - Show details - Vis detaljer - - - - Done - Ferdig - - - - TorConfigurationPage - - - Does this computer need a proxy to access the internet? - Trenger denne datamaskinen proxy for å få tilgang til Internett? - - - - Proxy type: - Proxytype: - - - - - None - Ingen - - - - Address: - Adresse: - - - - IP address or hostname - IP-adresse eller vertsnavn - - - - Port: - Port: - - - - Username: - Brukernavn: - - - - - Optional - Valgfri - - - - Password: - Passord: - - - - Does this computer's Internet connection go through a firewall that only allows connections to certain ports? - Går denne datamaskinens internettforbindelse gjennom en brannmur som kun tillater forbindelser til bestemte porter? - - - - Allowed ports: - Tillatte porter: - - - - Example: 80,443 - Eksempel: 80,443 - - - - If this computer's Internet connection is censored, you will need to obtain and use bridge relays. - Hvis denne datamaskinens internettforbindelse er sensurert må du få tak i og bruke broreléer. - - - - Enter one or more bridge relays (one per line): - Skriv inn et eller flere broreléer (et relé per linje): - - - - Back - Tilbake - - - - Connect - Koble til - - - - TorPreferences - - - Running: - Kjører: - - - - - - Yes - Ja - - - - - - No - Nei - - - - External - Ekstern - - - - Control connected: - Kontroll tilkoblet: - - - - Circuits established: - Etablerte kretser: - - - - Hidden service: - Skjult tjeneste: - - - - Online - Pålogget - - - - Offline - Frakoblet - - - - Version: - Versjon: - - - - Error: <b>%1</b> - %1 is error message - Feil: <b>%1</b> - - - - Configure - Konfigurer - - - - TorStateWidget - - - - Connection failed - Tilkoblingsfeil - - - - - Connecting… - \u2026 is ellipsis - Kobler til... - - - - Connecting… (%1%) - %1 is progress percentage, e.g. 100 - Kobler til... (%1%) - - - - Online - Pålogget - - - - Connected - Tilkoblet - - - diff --git a/translation/ricochet_nl_NL.ts b/translation/ricochet_nl_NL.ts deleted file mode 100644 index 9ea7ec04..00000000 --- a/translation/ricochet_nl_NL.ts +++ /dev/null @@ -1,701 +0,0 @@ - - - - - AboutPreferences - - - Ricochet %1 - %1 version, e.g. 1.0.0 - Ricochet %1 - - - - AddContactDialog - - - Share your Ricochet ID to allow connection requests - Deel je Ricochet ID om verbindingsverzoeken mogelijk te maken - - - - Cancel - Annuleren - - - - Add - Toevoegen - - - - ContactActions - - - Open Window - Open venster - - - - Details... - Details... - - - - Rename - Hernoemen - - - - Remove - Verwijderen - - - - ContactIDField - - - <b>%1</b> is already your contact - <b>%1</b> is al een contact - - - - You can't add yourself as a contact - Je kunt jezelf niet als contact toevoegen - - - - Enter an ID starting with <b>ricochet:</b> - Geef een ID op dat begint met <b>ricochet:</b> - - - - Copied to clipboard - Gekopieerd naar klembord - - - - Copy - Kopieer - - - - ContactList - - - Online - Online - - - - Offline - Offline - - - - Requests - Aanvragen - - - - Rejected - Afgewezen - - - - Outdated - Verouderd - - - - ContactPreferences - - - Date added: - Datum toegevoegd: - - - - Last seen: - Laatst gezien: - - - - Request: - Aanvraag: - - - - Pending connection - Verbinding wachtend - - - - Delivered - Afgeleverd - - - - Accepted - Geaccepteerd - - - - Error - Fout - - - - Rejected - Afgewezen - - - - %1 (Connected) - %1 status, e.g. "Accepted" - %1 (verbonden) - - - - Response: - Antwoord: - - - - Rename - Hernoemen - - - - Remove - Verwijder - - - - ContactRequestDialog - - - Someone new is asking to connect to you - Een nieuwe contactpersoon wil met je verbinden - - - - Reject - Afwijzen - - - - Accept - Accepteren - - - - ContactRequestFields - - - ID: - ID: - - - - Name: - Naam: - - - - Message: - Bericht: - - - - GeneralPreferences - - - Use a single window for conversations - Gebruik een enkel venster voor gesprekken - - - - Open links in default browser without prompting - Open links in standaardbrowser zonder te vragen - - - - Play audio notifications - Afspelen audio meldingen - - - - Volume - Volume - - - - Language - Taal - - - - Restart Ricochet to apply changes - Herstart Ricochet om wijzigingen door te voeren - - - - LanguagePreferences - - Select Language - Kies taal - - - Restart Ricochet to apply changes - Herstart Ricochet om wijzigingen door te voeren - - - - LanguagesModel - - - System default - Systeemstandaard - - - - Main - - - Ricochet Error - Ricochet fout - - - - MainToolBar - - - Add Contact - Toevoegen contact - - - - Preferences - Voorkeuren - - - - Click to add contacts - Klikken om contacten toe te voegen - - - - MainWindow - - - Remove %1 - Verwijder %1 - - - - Do you want to permanently remove %1? - Wil je %1 permanent verwijderen? - - - - MessageDelegate - - - %1 is offline - %1 nickname - %1 is offline - - - - Copy ID - Kopieer ID - - - - Copy Link - Kopieer link - - - - Open with Browser - Open met browser - - - - Add as Contact - Toevoegen als contact - - - - Copy Message - Kopiëren bericht - - - - Copy Selection - Kopiëren selectie - - - - MessageDialogWrapper - - - Remove %1 - Verwijder %1 - - - - Do you want to permanently remove %1? - %1 nickname - Wil je %1 permanent verwijderen? - - - - This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. - Deze contactpersoon kan je niet langer berichten sturen en wordt geïnformeerd over het verwijderen. Hij/zij kan een nieuwe aanvraag doen. - - - - NetworkSetupWizard - - - This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. - De internetverbinding van deze computer kent geen belemmeringen. Ik wil rechtstreeks verbinden met het TOR netwerk. - - - - Connect - Verbinden - - - - This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. - De internetverbinding van deze computer is gecensureerd, gefilterd of geproxied. Ik moet de netwerkinstellingen configureren. - - - - Configure - Configureren - - - - OfflineStateItem - - - Configure - Configureren - - - - Details - Details - - - - Connection failed - Verbinding mislukt - - - - Connecting… - \u2026 is ellipsis - Verbinden... - - - - OpenBrowserDialog - - - <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. - <b>Waarschuwing!</b> Het openen van links met je standaard browser schaadt je beveliging en anonimiteit.<br><br>Je kunt in plaats daarvan <a href='.'>kopiëren maar het klembord</a>. - - - - Don't ask again for links from %1 - Vraag niet opnieuw om links van %1 - - - - Don't ask again for any links (not recommended!) - Vraag niet meer naar links (niet aanbevolen!) - - - - Open Browser - Open Browser - - - - Cancel - Annuleren - - - - PreferencesDialog - - - Ricochet Preferences - Ricochet voorkeuren - - - - General - Algemeen - - - Language - Taal - - - - Contacts - Contacten - - - - Tor - Tor - - - - About - Over - - - - QCocoaMenuItem - - - Preference - Voorkeur - - - - StartupStatusPage - - - The Tor process was not started successfully. This is most likely an installation or system error. - Het TOR proces is niet succesvol gestart. Dit komt waarschijnlijk door een installatie- of systeemfout. - - - - Quit - Afsluiten - - - - TorBootstrapStatus - - - Connecting to the Tor network… - \u2026 is ellipsis - Verbinden met het TOR netwerk... - - - - Back - Terug - - - - Hide details - Verberg details - - - - Show details - Toon details - - - - Done - Gedaan - - - - TorConfigurationPage - - - Does this computer need a proxy to access the internet? - Heeft deze computer een proxy nodig om te verbinden met het internet? - - - - Proxy type: - Proxy type: - - - - - None - Geen - - - - Address: - Adres: - - - - IP address or hostname - IP adres of servernaam - - - - Port: - Poort: - - - - Username: - Gebruikersnaam: - - - - - Optional - Optioneel - - - - Password: - Wachtwoord: - - - - Does this computer's Internet connection go through a firewall that only allows connections to certain ports? - Gaat de internetverbinding van deze computer door een firewall die alleen verbindingen naar bepaalde poorten toestaat? - - - - Allowed ports: - Toegestane poorten: - - - - Example: 80,443 - Voorbeeld: 80,443 - - - - If this computer's Internet connection is censored, you will need to obtain and use bridge relays. - Als de internetverbinding van deze computer is gecensureerd, moet je bridge relays vinden en gebruiken. - - - - Enter one or more bridge relays (one per line): - Geen een of meer bridge relays op (een per regel): - - - - Back - Terug - - - - Connect - Verbinden - - - - TorPreferences - - - Running: - Draaiend: - - - - - - Yes - Ja - - - - - - No - Nee - - - - External - Extern - - - - Control connected: - Control verbonden: - - - - Circuits established: - Circuits aangelegd: - - - - Hidden service: - Verborgen service: - - - - Online - Online - - - - Offline - Offline - - - - Version: - Versie: - - - - Error: <b>%1</b> - %1 is error message - Fout: <b>%1</b> - - - - Configure - Configureren - - - - TorStateWidget - - - - Connection failed - Verbinding mislukt - - - - - Connecting… - \u2026 is ellipsis - Verbinden... - - - - Connecting… (%1%) - %1 is progress percentage, e.g. 100 - Verbinden… (%1%) - - - - Online - Online - - - - Connected - Verbonden - - - diff --git a/translation/ricochet_pl.ts b/translation/ricochet_pl.ts deleted file mode 100644 index a62ed7a7..00000000 --- a/translation/ricochet_pl.ts +++ /dev/null @@ -1,701 +0,0 @@ - - - - - AboutPreferences - - - Ricochet %1 - %1 version, e.g. 1.0.0 - Ricochet %1 - - - - AddContactDialog - - - Share your Ricochet ID to allow connection requests - Udostępnij swój identyfikator Ricochet, aby umożliwić łączenie się z Tobą. - - - - Cancel - Anuluj - - - - Add - Dodaj - - - - ContactActions - - - Open Window - Otwórz okno - - - - Details... - Szczegóły... - - - - Rename - Zmień nazwę - - - - Remove - Usuń - - - - ContactIDField - - - <b>%1</b> is already your contact - <b>%1</b> jest już w Twoich kontaktach - - - - You can't add yourself as a contact - Nie możesz dodać siebie do kontaktów - - - - Enter an ID starting with <b>ricochet:</b> - Wpisz ID zaczynające się od <b>ricochet:</b> - - - - Copied to clipboard - Skopiowano do schowka - - - - Copy - Kopiuj - - - - ContactList - - - Online - Połączeni - - - - Offline - Rozłączeni - - - - Requests - Prośby o dodanie - - - - Rejected - Odrzucone - - - - Outdated - Przeterminowane - - - - ContactPreferences - - - Date added: - Dołączył: - - - - Last seen: - Ostatnio widziany: - - - - Request: - Prośba o dodanie: - - - - Pending connection - Oczekuje na połączenie - - - - Delivered - Dostarczony - - - - Accepted - Zaakceptowany - - - - Error - Błąd - - - - Rejected - Odrzucony - - - - %1 (Connected) - %1 status, e.g. "Accepted" - %1 (Połączony) - - - - Response: - Odpowiedź: - - - - Rename - Zmień nazwę - - - - Remove - Usuń - - - - ContactRequestDialog - - - Someone new is asking to connect to you - Ktoś chce się z Tobą skontaktować - - - - Reject - Odrzuć - - - - Accept - Akceptuj - - - - ContactRequestFields - - - ID: - ID: - - - - Name: - Nazwa: - - - - Message: - Wiadomość: - - - - GeneralPreferences - - - Use a single window for conversations - Używaj pojedynczego okno do rozmów - - - - Open links in default browser without prompting - Otwórz linki w domyślnej przeglądarce bez pytania - - - - Play audio notifications - Włącz powiadomienia dźwiękowe - - - - Volume - Głośność - - - - Language - Język - - - - Restart Ricochet to apply changes - Aby zaakceptować zmiany, uruchom Ricochet ponownie - - - - LanguagePreferences - - Select Language - Wybierz język - - - Restart Ricochet to apply changes - Aby zaakceptować zmiany, uruchom Ricochet ponownie - - - - LanguagesModel - - - System default - Domyślny w systemie - - - - Main - - - Ricochet Error - Błąd programu Ricochet - - - - MainToolBar - - - Add Contact - Dodaj kontakt - - - - Preferences - Ustawienia - - - - Click to add contacts - Kliknij, aby dodać do kontaktów - - - - MainWindow - - - Remove %1 - Usuń %1 - - - - Do you want to permanently remove %1? - Czy chcesz na stałe usunąć %1? - - - - MessageDelegate - - - %1 is offline - %1 nickname - %1 jest rozłączony - - - - Copy ID - Skopiuj ID - - - - Copy Link - Skopiuj link - - - - Open with Browser - Otwórz w przeglądarce - - - - Add as Contact - Dodaj jako kontakt - - - - Copy Message - Kopiuj wiadomość - - - - Copy Selection - Kopiuj zaznaczenie - - - - MessageDialogWrapper - - - Remove %1 - Usuń %1 - - - - Do you want to permanently remove %1? - %1 nickname - Czy chcesz na stałe usunąć %1? - - - - This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. - Ten kontakt nie będzie mógł więcej wysyłać do Ciebie wiadomości i zostanie poinformowany o usunięciu. Może wysłać Ci nową prośbę o połączenie. - - - - NetworkSetupWizard - - - This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. - Połączenie internetowe tego kompuera jest wolne od przeszkód. Chcę połączyć się bezpośrednio z siecią Tor. - - - - Connect - Połącz - - - - This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. - Połączenie internetowe tego komputera jest cenzurowane, filtrowane lub trasowane przez serwer proxy. Muszę skonfigurować ustawienia sieciowe. - - - - Configure - Konfiguruj - - - - OfflineStateItem - - - Configure - Konfiguruj - - - - Details - Szczegóły - - - - Connection failed - Połączenie nie powiodło się - - - - Connecting… - \u2026 is ellipsis - Łączenie… - - - - OpenBrowserDialog - - - <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. - <b>Uwaga!</b> Otwieranie linków za pomocą domyślnej przeglądarki spowoduje utratę bezpieczeństwa i anonimowości. Zamiast tego możesz <a href='.'>skopiować go do schowka</a> - - - - Don't ask again for links from %1 - Nie pytaj ponownie dla linków od %1 - - - - Don't ask again for any links (not recommended!) - Nigdy nie pytaj ponownie (niezalecane!) - - - - Open Browser - Otwórz przeglądarkę - - - - Cancel - Anuluj - - - - PreferencesDialog - - - Ricochet Preferences - Ustawienia Ricocheta - - - - General - Główne - - - Language - Język - - - - Contacts - Kontakty - - - - Tor - Tor - - - - About - O programie - - - - QCocoaMenuItem - - - Preference - Ustawienie - - - - StartupStatusPage - - - The Tor process was not started successfully. This is most likely an installation or system error. - Tor nie został uruchomiony poprawnie. Najprawdopodobniej oznacza to błąd instalacji lub systemu. - - - - Quit - Wyjdź - - - - TorBootstrapStatus - - - Connecting to the Tor network… - \u2026 is ellipsis - Łączenie z siecią Tor… - - - - Back - Wróć - - - - Hide details - Ukryj szczegóły - - - - Show details - Pokaż szczegóły - - - - Done - Zrobione - - - - TorConfigurationPage - - - Does this computer need a proxy to access the internet? - Czy ten komputer potrzebuje serwera proxy, aby łączyć się z Internetem? - - - - Proxy type: - Typ proxy: - - - - - None - Brak - - - - Address: - Adres: - - - - IP address or hostname - Adres IP lub nazwa hosta - - - - Port: - Port: - - - - Username: - Nazwa użytkownika: - - - - - Optional - Opcjonalny - - - - Password: - Hasło: - - - - Does this computer's Internet connection go through a firewall that only allows connections to certain ports? - Czy połączenie internetowe tego komputera jest kierowane przez firewall, który pozwala na łączenie się tylko z wybranymi portami? - - - - Allowed ports: - Dozwolone porty: - - - - Example: 80,443 - Na przykład: 80,443 - - - - If this computer's Internet connection is censored, you will need to obtain and use bridge relays. - Jeżeli połączenie internetowe tego komputera jest cenzurowane, będziesz musiał uzyskać dostęp do węzła pośredniego (<i>bridge relay</i>). - - - - Enter one or more bridge relays (one per line): - Podaj jeden lub więcej węzłów pośrednich (jeden w linii): - - - - Back - Wróć - - - - Connect - Połącz - - - - TorPreferences - - - Running: - Uruchomione: - - - - - - Yes - Tak - - - - - - No - Nie - - - - External - Zewnętrzny - - - - Control connected: - Połączony z portem kontrolnym: - - - - Circuits established: - Obwody utworzone: - - - - Hidden service: - Ukryte usługi: - - - - Online - Połączony - - - - Offline - Rozłączony - - - - Version: - Wersja: - - - - Error: <b>%1</b> - %1 is error message - Błąd: <b>%1</b> - - - - Configure - Konfiguruj - - - - TorStateWidget - - - - Connection failed - Połączenie nie powiodło się - - - - - Connecting… - \u2026 is ellipsis - Łączenie… - - - - Connecting… (%1%) - %1 is progress percentage, e.g. 100 - Łączenie… (%1%) - - - - Online - Połączony - - - - Connected - Połączony - - - diff --git a/translation/ricochet_pt_BR.ts b/translation/ricochet_pt_BR.ts deleted file mode 100644 index f13ce46a..00000000 --- a/translation/ricochet_pt_BR.ts +++ /dev/null @@ -1,701 +0,0 @@ - - - - - AboutPreferences - - - Ricochet %1 - %1 version, e.g. 1.0.0 - Ricochet %1 - - - - AddContactDialog - - - Share your Ricochet ID to allow connection requests - Compartilhe seu ID Ricochet para permitir pedidos de conexão - - - - Cancel - Cancelar - - - - Add - Adicionar - - - - ContactActions - - - Open Window - Abrir Janela - - - - Details... - Detalhes... - - - - Rename - Renomear - - - - Remove - Remover - - - - ContactIDField - - - <b>%1</b> is already your contact - <b>%1</b> já é seu contato - - - - You can't add yourself as a contact - Você não pode adicionar a si mesmo como um contato - - - - Enter an ID starting with <b>ricochet:</b> - Entre uma ID começando com <b>ricochet:</b> - - - - Copied to clipboard - Copiado para a área de transferência - - - - Copy - Copiar - - - - ContactList - - - Online - Online - - - - Offline - Offline - - - - Requests - Pedidos - - - - Rejected - Rejeitado - - - - Outdated - Desatualizado - - - - ContactPreferences - - - Date added: - Adicionado em: - - - - Last seen: - Visto pela última vez: - - - - Request: - Pedido: - - - - Pending connection - Conexão pendente - - - - Delivered - Entregue - - - - Accepted - Aceito - - - - Error - Erro - - - - Rejected - Rejeitado - - - - %1 (Connected) - %1 status, e.g. "Accepted" - %1 (Conectado) - - - - Response: - Resposta: - - - - Rename - Renomear - - - - Remove - Remover - - - - ContactRequestDialog - - - Someone new is asking to connect to you - Alguém novo está pedindo para se conectar com você - - - - Reject - Rejeitar - - - - Accept - Aceitar - - - - ContactRequestFields - - - ID: - ID: - - - - Name: - Nome: - - - - Message: - Mensagem: - - - - GeneralPreferences - - - Use a single window for conversations - Usar somente uma janela para conversas - - - - Open links in default browser without prompting - Abrir links no navegador padrão sem pedir confirmação - - - - Play audio notifications - Tocar notificações de áudio - - - - Volume - Volume - - - - Language - Idioma - - - - Restart Ricochet to apply changes - Reiniciar Ricochet para aplicar mudanças - - - - LanguagePreferences - - Select Language - Selecionar Linguagem - - - Restart Ricochet to apply changes - Reiniciar Ricochet para aplicar mudanças - - - - LanguagesModel - - - System default - Padrão do sistema - - - - Main - - - Ricochet Error - Erro do Ricochet - - - - MainToolBar - - - Add Contact - Adicionar Contato - - - - Preferences - Preferências - - - - Click to add contacts - Clique para adicionar contatos - - - - MainWindow - - - Remove %1 - Remover %1 - - - - Do you want to permanently remove %1? - Você deseja remover %1 permanentemente? - - - - MessageDelegate - - - %1 is offline - %1 nickname - %1 está offline - - - - Copy ID - Copiar ID - - - - Copy Link - Copiar Link - - - - Open with Browser - Abrir no Navegador - - - - Add as Contact - Adicionar como Contato - - - - Copy Message - Copiar Mensagem - - - - Copy Selection - Copiar Seleção - - - - MessageDialogWrapper - - - Remove %1 - Remover %1 - - - - Do you want to permanently remove %1? - %1 nickname - Você deseja remover %1 permanentemente? - - - - This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. - Este contato não poderá mais te enviar mensagens, e será notificado sobre a remoção. Ele pode escolher enviar novo pedido de conexão. - - - - NetworkSetupWizard - - - This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. - A conexão à internet deste computador está livre de obstáculos. Quero me conectar diretamente à rede Tor. - - - - Connect - Conectar - - - - This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. - A conexão à internet deste computador é censurada, filtrada, ou passa por proxy. Preciso configurar a rede. - - - - Configure - Configurar - - - - OfflineStateItem - - - Configure - Configurar - - - - Details - Detalhes - - - - Connection failed - A conexão falhou - - - - Connecting… - \u2026 is ellipsis - Conectando... - - - - OpenBrowserDialog - - - <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. - <b>Atenção!</b> Abrir links com o seu navegador pode danificar sua segurança e anonimidade.<br><br>Ao invés disso, você pode <a href='.'>copiar para a área de transferência</a>. - - - - Don't ask again for links from %1 - Não perguntar de novo para links de %1 - - - - Don't ask again for any links (not recommended!) - Não perguntar de novo para quaisquer links (não recomendado!) - - - - Open Browser - Abrir Navegador - - - - Cancel - Cancelar - - - - PreferencesDialog - - - Ricochet Preferences - Preferências do Ricochet - - - - General - Geral - - - Language - Idioma - - - - Contacts - Contatos - - - - Tor - Tor - - - - About - Sobre - - - - QCocoaMenuItem - - - Preference - Preferência - - - - StartupStatusPage - - - The Tor process was not started successfully. This is most likely an installation or system error. - O processo do Tor não foi iniciado com sucesso. Muito provavelmente isso é um erro de instalação ou de sistema. - - - - Quit - Sair - - - - TorBootstrapStatus - - - Connecting to the Tor network… - \u2026 is ellipsis - Conectando à rede Tor... - - - - Back - Voltar - - - - Hide details - Esconder detalhes - - - - Show details - Mostrar detalhes - - - - Done - Feito - - - - TorConfigurationPage - - - Does this computer need a proxy to access the internet? - Este computador precisa de um proxy para acessar a internet? - - - - Proxy type: - Tipo de proxy: - - - - - None - Nenhum - - - - Address: - Endereço: - - - - IP address or hostname - Endereço IP ou nome do host - - - - Port: - Porta: - - - - Username: - Usuário: - - - - - Optional - Opcional - - - - Password: - Senha: - - - - Does this computer's Internet connection go through a firewall that only allows connections to certain ports? - A conexão à internet deste computador passa por um firewall que só permite conexões a certas portas? - - - - Allowed ports: - Portas permitidas: - - - - Example: 80,443 - Exemplo: 80,443 - - - - If this computer's Internet connection is censored, you will need to obtain and use bridge relays. - Se a conexão à internet deste computador é censurada, você precisará obter e usar bridge relays. - - - - Enter one or more bridge relays (one per line): - Entre um ou mais bridge relays (um por linha): - - - - Back - Voltar - - - - Connect - Conectar - - - - TorPreferences - - - Running: - Executando: - - - - - - Yes - Sim - - - - - - No - Não - - - - External - Externo - - - - Control connected: - Controle conectado: - - - - Circuits established: - Circuitos estabelecidos: - - - - Hidden service: - Serviço escondido: - - - - Online - Online - - - - Offline - Offline - - - - Version: - Versão: - - - - Error: <b>%1</b> - %1 is error message - Erro: <b>%1</b> - - - - Configure - Configurar - - - - TorStateWidget - - - - Connection failed - A conexão falhou - - - - - Connecting… - \u2026 is ellipsis - Conectando... - - - - Connecting… (%1%) - %1 is progress percentage, e.g. 100 - Conectando... (%1) - - - - Online - Online - - - - Connected - Conectado - - - diff --git a/translation/ricochet_pt_PT.ts b/translation/ricochet_pt_PT.ts deleted file mode 100644 index 5279db3b..00000000 --- a/translation/ricochet_pt_PT.ts +++ /dev/null @@ -1,701 +0,0 @@ - - - - - AboutPreferences - - - Ricochet %1 - %1 version, e.g. 1.0.0 - Ricochet %1 - - - - AddContactDialog - - - Share your Ricochet ID to allow connection requests - Partilhe o seu ID Ricochet para permitir pedidos de ligação - - - - Cancel - Cancelar - - - - Add - Adicionar - - - - ContactActions - - - Open Window - Abrir Janela - - - - Details... - Detalhes... - - - - Rename - Mudar o nome - - - - Remove - Remover - - - - ContactIDField - - - <b>%1</b> is already your contact - <b>%1</b> já é um contacto seu - - - - You can't add yourself as a contact - Não pode adicionar-se a si mesmo como contacto - - - - Enter an ID starting with <b>ricochet:</b> - Insira um ID que comece com <b>ricochet:</b> - - - - Copied to clipboard - Copiado para a área de transferência - - - - Copy - Copiar - - - - ContactList - - - Online - Online - - - - Offline - Offline - - - - Requests - Pedidos - - - - Rejected - Rejeitados - - - - Outdated - Desatualizado - - - - ContactPreferences - - - Date added: - Adicionado a: - - - - Last seen: - Visto pela última vez: - - - - Request: - Pedido: - - - - Pending connection - Ligação pendente - - - - Delivered - Enviado - - - - Accepted - Aceite - - - - Error - Erro - - - - Rejected - Rejeitado - - - - %1 (Connected) - %1 status, e.g. "Accepted" - %1 (Ligado) - - - - Response: - Resposta: - - - - Rename - Mudar o nome - - - - Remove - Remover - - - - ContactRequestDialog - - - Someone new is asking to connect to you - Alguém novo está a pedir para se ligar a si - - - - Reject - Rejeitar - - - - Accept - Aceitar - - - - ContactRequestFields - - - ID: - ID: - - - - Name: - Nome: - - - - Message: - Mensagem: - - - - GeneralPreferences - - - Use a single window for conversations - Utilizar uma única janela para conversas - - - - Open links in default browser without prompting - Abrir links no navegador padrão sem pedir confirmação - - - - Play audio notifications - Ativar notificações de áudio - - - - Volume - Volume - - - - Language - Idioma - - - - Restart Ricochet to apply changes - Reiniciar o Ricochet para aplicar as alterações - - - - LanguagePreferences - - Select Language - Selecionar Idioma - - - Restart Ricochet to apply changes - Reiniciar o Ricochet para aplicar as alterações - - - - LanguagesModel - - - System default - Padrão do sistema - - - - Main - - - Ricochet Error - Erro do Ricochet - - - - MainToolBar - - - Add Contact - Adicionar Contacto - - - - Preferences - Preferências - - - - Click to add contacts - Clique para adicionar contactos - - - - MainWindow - - - Remove %1 - Remover %1 - - - - Do you want to permanently remove %1? - Deseja remover %1 permanentemente? - - - - MessageDelegate - - - %1 is offline - %1 nickname - %1 está offline - - - - Copy ID - Copiar ID - - - - Copy Link - Copiar Link - - - - Open with Browser - Abrir com o Navegador - - - - Add as Contact - Adicionar como Contacto - - - - Copy Message - Copiar Mensagem - - - - Copy Selection - Copiar Seleção - - - - MessageDialogWrapper - - - Remove %1 - Remover %1 - - - - Do you want to permanently remove %1? - %1 nickname - Deseja remover %1 permanentemente? - - - - This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. - Este contacto deixará de lhe poder enviar mensagens, e será ainda notificado sobre a remoção. No entanto, este poderá enviar um novo pedido de ligação. - - - - NetworkSetupWizard - - - This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. - A ligação à Internet deste computador está livre de obstáculos. Pretendo ligar-me diretamente à rede Tor. - - - - Connect - Ligar - - - - This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. - A ligação à Internet deste computador é censurada, filtrada, ou passa por um proxy. Necessito de configurar as definições da rede. - - - - Configure - Configurar - - - - OfflineStateItem - - - Configure - Configurar - - - - Details - Detalhes - - - - Connection failed - Ligação falhou - - - - Connecting… - \u2026 is ellipsis - Ligando... - - - - OpenBrowserDialog - - - <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. - <b>Atenção!</b> Abrir links com o seu navegador padrão irá diminuir a sua segurança e anonimidade.<br><br>Em vez disso, poderá <a href='.'>copiar para a área de transferência</a>. - - - - Don't ask again for links from %1 - Não perguntar de novo para links de %1 - - - - Don't ask again for any links (not recommended!) - Não perguntar de novo para quaisquer links (não recomendado!) - - - - Open Browser - Abrir Navegador - - - - Cancel - Cancelar - - - - PreferencesDialog - - - Ricochet Preferences - Preferências do Ricochet - - - - General - Geral - - - Language - Idioma - - - - Contacts - Contactos - - - - Tor - Tor - - - - About - Sobre - - - - QCocoaMenuItem - - - Preference - Preferência - - - - StartupStatusPage - - - The Tor process was not started successfully. This is most likely an installation or system error. - O processo do Tor não foi iniciado com sucesso. Tal deve-se, provavelmente, a um erro de instalação ou de sistema. - - - - Quit - Sair - - - - TorBootstrapStatus - - - Connecting to the Tor network… - \u2026 is ellipsis - A ligar à rede Tor... - - - - Back - Voltar - - - - Hide details - Esconder detalhes - - - - Show details - Mostrar detalhes - - - - Done - Terminado - - - - TorConfigurationPage - - - Does this computer need a proxy to access the internet? - Este computador necessita de um proxy para aceder à internet? - - - - Proxy type: - Tipo de proxy: - - - - - None - Nenhum - - - - Address: - Endereço: - - - - IP address or hostname - Endereço de IP ou nome do host - - - - Port: - Porta: - - - - Username: - Nome de utilizador: - - - - - Optional - Opcional - - - - Password: - Palavra-passe: - - - - Does this computer's Internet connection go through a firewall that only allows connections to certain ports? - A ligação à Internet deste computador passa por uma firewall que apenas permite ligações por certas portas? - - - - Allowed ports: - Portas permitidas: - - - - Example: 80,443 - Exemplo: 80,443 - - - - If this computer's Internet connection is censored, you will need to obtain and use bridge relays. - Se a ligação à Internet deste computador é censurada, necessitará de obter e utilizar bridge relays. - - - - Enter one or more bridge relays (one per line): - Insira um ou mais bridge relays (um por linha): - - - - Back - Voltar - - - - Connect - Ligar - - - - TorPreferences - - - Running: - A correr: - - - - - - Yes - Sim - - - - - - No - Não - - - - External - Externo - - - - Control connected: - Controlo ligado: - - - - Circuits established: - Circuitos estabelecidos: - - - - Hidden service: - Serviço oculto: - - - - Online - Online - - - - Offline - Offline - - - - Version: - Versão: - - - - Error: <b>%1</b> - %1 is error message - Erro: <b>%1</b> - - - - Configure - Configurar - - - - TorStateWidget - - - - Connection failed - Ligação falhou - - - - - Connecting… - \u2026 is ellipsis - Ligando... - - - - Connecting… (%1%) - %1 is progress percentage, e.g. 100 - Ligando... (%1%) - - - - Online - Online - - - - Connected - Ligado - - - diff --git a/translation/ricochet_ru.ts b/translation/ricochet_ru.ts deleted file mode 100644 index 443e7041..00000000 --- a/translation/ricochet_ru.ts +++ /dev/null @@ -1,686 +0,0 @@ - - - - - AboutPreferences - - - Ricochet %1 - %1 version, e.g. 1.0.0 - Ricochet %1 - - - - AddContactDialog - - - Share your Ricochet ID to allow connection requests - Сообщите свой ID, чтобы собеседник узнал Вас - - - - Cancel - Отмена - - - - Add - Добавить - - - - ContactActions - - - Open Window - Открыть окно - - - - Details... - Подробнее... - - - - Rename - Переименовать - - - - Remove - Удалить - - - - ContactIDField - - - <b>%1</b> is already your contact - <b>%1</b> уже в Вашем списке контактов - - - - You can't add yourself as a contact - Вы не можете добавить себя в свой же список контактов - - - - Enter an ID starting with <b>ricochet:</b> - Введите ID, начинающийся с <b>ricochet:</b> - - - - Copied to clipboard - Скопировано в буфер - - - - Copy - Копировать - - - - ContactList - - - Online - В сети - - - - Offline - Не в сети - - - - Requests - Запросы - - - - Rejected - Отказано - - - - Outdated - - - - - ContactPreferences - - - Date added: - Добавлен: - - - - Last seen: - Последний раз замечен: - - - - Request: - Запрос: - - - - Pending connection - Ожидаем соединение - - - - Delivered - Доставлено - - - - Accepted - Принято - - - - Error - Ошибка - - - - Rejected - Отказано - - - - %1 (Connected) - %1 status, e.g. "Accepted" - %1 (Соединено) - - - - Response: - Ответ: - - - - Rename - Переименовать - - - - Remove - Удалить - - - - ContactRequestDialog - - - Someone new is asking to connect to you - Новый человек хочет пообщаться с Вами - - - - Reject - Отказать - - - - Accept - Принять - - - - ContactRequestFields - - - ID: - ID: - - - - Name: - Имя: - - - - Message: - Сообщение: - - - - GeneralPreferences - - - Use a single window for conversations - Использовать одно окно для разговоров - - - - Open links in default browser without prompting - Открывать ссылки в браузере по умолчанию без запроса - - - - Play audio notifications - - - - - Volume - - - - - Language - - - - - Restart Ricochet to apply changes - - - - - LanguagesModel - - - System default - - - - - Main - - - Ricochet Error - Ошибка Ricochet - - - - MainToolBar - - - Add Contact - Добавить контакт - - - - Preferences - Настройки - - - - Click to add contacts - Нажмите, чтобы добавить контакты - - - - MainWindow - - - Remove %1 - Удалить %1 - - - - Do you want to permanently remove %1? - Вы действительно хотите удалить %1 навсегда? - - - - MessageDelegate - - - %1 is offline - %1 nickname - %1 не в сети - - - - Copy ID - Копировать ID - - - - Copy Link - Копировать ссылку - - - - Open with Browser - Открыть в браузере - - - - Add as Contact - Добавить в список контактов - - - - Copy Message - - - - - Copy Selection - - - - - MessageDialogWrapper - - - Remove %1 - Удалить %1 - - - - Do you want to permanently remove %1? - %1 nickname - Вы действительно хотите удалить %1 навсегда? - - - - This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. - Этот контакт больше не сможет писать Вам и будет уведомлен об удалении. Тем не менее, он сможет послать Вам новый запрос. - - - - NetworkSetupWizard - - - This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. - Соединение на этом компьютере свободно от цензуры и фильтров. Я хочу подключиться к сети Tor напрямую. - - - - Connect - Соединиться - - - - This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. - Соединение на этом компьютере подвергается цензуре, фильтрации либо проксируется. Мне нужно настроить дополнительные параметры соединения. - - - - Configure - Настроить - - - - OfflineStateItem - - - Configure - Настроить - - - - Details - Подробности - - - - Connection failed - Не удалось соединиться - - - - Connecting… - \u2026 is ellipsis - Соединение... - - - - OpenBrowserDialog - - - <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. - <b>Внимание!</b> Открывая ссылки браузером по умолчанию Вы можете нарушить Вашу безопасность и анонимность.<br><br>Вместо этого Вы можете <a href='.'>скопировать ссылку</a>. - - - - Don't ask again for links from %1 - Не спрашивать при открытии последующих ссылок от %1 - - - - Don't ask again for any links (not recommended!) - Не спрашивать при открытии всех последующих ссылок (не рекомендуется!) - - - - Open Browser - Открыть браузер - - - - Cancel - Отмена - - - - PreferencesDialog - - - Ricochet Preferences - Настройки Ricochet - - - - General - Основные - - - - Contacts - Контакты - - - - Tor - Tor - - - - About - О программе - - - - QCocoaMenuItem - - - Preference - Настройки - - - - StartupStatusPage - - - The Tor process was not started successfully. This is most likely an installation or system error. - Процесс Tor не был запущен успешно. Скорее всего, причина в неправильной установке или системной ошибке. - - - - Quit - Выход - - - - TorBootstrapStatus - - - Connecting to the Tor network… - \u2026 is ellipsis - Подсоединяемся к сети Tor… - - - - Back - Назад - - - - Hide details - Скрыть подробности - - - - Show details - Показать подробности - - - - Done - Готово - - - - TorConfigurationPage - - - Does this computer need a proxy to access the internet? - Этот компьютер использует прокси для доступа в Интернет? - - - - Proxy type: - Тип прокси: - - - - - None - Нет - - - - Address: - Адрес: - - - - IP address or hostname - IP адрес или имя хоста - - - - Port: - Порт: - - - - Username: - Имя пользователя: - - - - - Optional - Необязательно - - - - Password: - Пароль: - - - - Does this computer's Internet connection go through a firewall that only allows connections to certain ports? - Соединение данного компьютера фильтруется фаерволом, который разрешает доступ только к определенным портам? - - - - Allowed ports: - Открытые порты: - - - - Example: 80,443 - Например: 80,443 - - - - If this computer's Internet connection is censored, you will need to obtain and use bridge relays. - Если соединение этого компьютера подвергается цензуре, Вам необходимо получить и использовать ретрансляторы Tor. - - - - Enter one or more bridge relays (one per line): - Введите один ретранслятор или более (по одному на строку): - - - - Back - Назад - - - - Connect - Соединить - - - - TorPreferences - - - Running: - Запущен: - - - - - - Yes - Да - - - - - - No - Нет - - - - External - Внешний - - - - Control connected: - Контроль присоединенных: - - - - Circuits established: - Цепи установлены: - - - - Hidden service: - Скрытый сервис: - - - - Online - В сети - - - - Offline - Не в сети - - - - Version: - Версия: - - - - Error: <b>%1</b> - %1 is error message - Ошибка: <b>%1</b> - - - - Configure - Настроить - - - - TorStateWidget - - - - Connection failed - Не удалось соединиться - - - - - Connecting… - \u2026 is ellipsis - Соединение… - - - - Connecting… (%1%) - %1 is progress percentage, e.g. 100 - Соединение… (%1%) - - - - Online - В сети - - - - Connected - Соединено - - - diff --git a/translation/ricochet_sl.ts b/translation/ricochet_sl.ts deleted file mode 100644 index 2c3e8859..00000000 --- a/translation/ricochet_sl.ts +++ /dev/null @@ -1,701 +0,0 @@ - - - - - AboutPreferences - - - Ricochet %1 - %1 version, e.g. 1.0.0 - Ricochet %1 - - - - AddContactDialog - - - Share your Ricochet ID to allow connection requests - Za povezavo z drugimi posredujte svoj Ricochet ID. - - - - Cancel - Prekliči - - - - Add - Dodaj - - - - ContactActions - - - Open Window - Odpri Okno - - - - Details... - Podrobnosti... - - - - Rename - Preimenuj - - - - Remove - Odstrani - - - - ContactIDField - - - <b>%1</b> is already your contact - <b>%1</b> je že med stiki - - - - You can't add yourself as a contact - Sebe ne moreš dodati med stike - - - - Enter an ID starting with <b>ricochet:</b> - Vnesi ID, tako da začneš vnos z <b>ricochet:</b> - - - - Copied to clipboard - Kopirano v odložišče - - - - Copy - Kopiraj - - - - ContactList - - - Online - Povezan - - - - Offline - Brez povezave - - - - Requests - Zahteve - - - - Rejected - Zavrnjeno - - - - Outdated - Zastarelo - - - - ContactPreferences - - - Date added: - Dodan datum: - - - - Last seen: - Nazadnje viden: - - - - Request: - Zahteva: - - - - Pending connection - Povezovanje je v teku - - - - Delivered - Dostavljeno - - - - Accepted - Sprejeto - - - - Error - Napaka - - - - Rejected - Zavrnjeno - - - - %1 (Connected) - %1 status, e.g. "Accepted" - %1 (Povezan) - - - - Response: - Odgovor: - - - - Rename - Preimenuj - - - - Remove - Odstrani - - - - ContactRequestDialog - - - Someone new is asking to connect to you - Nekdo nov se želi povezati z vami - - - - Reject - Zavrni - - - - Accept - Sprejmi - - - - ContactRequestFields - - - ID: - ID: - - - - Name: - Ime: - - - - Message: - Sporočilo: - - - - GeneralPreferences - - - Use a single window for conversations - Uporabljaj enotno okno za pogovore - - - - Open links in default browser without prompting - Brez spraševanja odpri povezave v privzetem brskalniku - - - - Play audio notifications - Predvajaj zvočna obvestila - - - - Volume - Glasnost - - - - Language - Jezik - - - - Restart Ricochet to apply changes - Za uveljavitev sprememb ponovno zaženite Ricochet - - - - LanguagePreferences - - Select Language - Izberi jezik - - - Restart Ricochet to apply changes - Za uveljavitev sprememb ponovno zaženite Ricochet - - - - LanguagesModel - - - System default - Sistemsko privzeto - - - - Main - - - Ricochet Error - Napaka Ricochet - - - - MainToolBar - - - Add Contact - Dodaj stik - - - - Preferences - Nastavitve - - - - Click to add contacts - Klikni za dodajanje kontaktov - - - - MainWindow - - - Remove %1 - Odstrani %1 - - - - Do you want to permanently remove %1? - Ali želte za vselej odstraniti %1? - - - - MessageDelegate - - - %1 is offline - %1 nickname - %1 ni povezan - - - - Copy ID - Kopiraj ID - - - - Copy Link - Kopiraj povezavo - - - - Open with Browser - Odpri z brskalnikom - - - - Add as Contact - Dodaj kot stik - - - - Copy Message - Kopiraj sporočilo - - - - Copy Selection - Kopiraj izbor - - - - MessageDialogWrapper - - - Remove %1 - Odstrani %1 - - - - Do you want to permanently remove %1? - %1 nickname - Ali želte za vselej odstraniti %1? - - - - This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. - Ta oseba vam ne bo mogla več pošiljati sporočil in bo obveščena o odstranitvi z vašega seznama. Lahko vam pošlje novo zahtevo za povezavo. - - - - NetworkSetupWizard - - - This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. - Povezava tega računalnika z medmrežjem je brez ovir. Želim se neposredno povezati z omrežjem Tor. - - - - Connect - Poveži se - - - - This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. - Povezava tega računalnika z medmrežjem je cenzurirana, filtrirana ali preusmerjena. Potrebne so ročne nastavitve. - - - - Configure - Nastavi - - - - OfflineStateItem - - - Configure - Nastavi - - - - Details - Podrobnosti - - - - Connection failed - Povezovanje ni uspelo - - - - Connecting… - \u2026 is ellipsis - Povezujem... - - - - OpenBrowserDialog - - - <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. - <b>Pozor!</b> Odpiranje povezav s privzetim brskalnikom škoduje vaši zasebnosti in varnosti.<br><br>Lahko pa jih namesto tega<a href='.'>prekopirate v odložišče</a>. - - - - Don't ask again for links from %1 - Ne sprašuj več za povezavo z %1 - - - - Don't ask again for any links (not recommended!) - Ne sprašuj več za nobeno povezavo (ni priporočljivo!) - - - - Open Browser - Odpri brskalnik - - - - Cancel - Prekliči - - - - PreferencesDialog - - - Ricochet Preferences - Nastavitve Ricochet - - - - General - Splošno - - - Language - Jezik - - - - Contacts - Stiki - - - - Tor - Tor - - - - About - Več o - - - - QCocoaMenuItem - - - Preference - Izbira - - - - StartupStatusPage - - - The Tor process was not started successfully. This is most likely an installation or system error. - Proces Tor se ni uspel uspešno zagnati. Najverjetneje gre za problem pri instalaciji ali sistemsko napako. - - - - Quit - Končaj - - - - TorBootstrapStatus - - - Connecting to the Tor network… - \u2026 is ellipsis - Povezujem se z omrežjem Tor... - - - - Back - Nazaj - - - - Hide details - Skrij podrobnosti - - - - Show details - Prikaži podrobnosti - - - - Done - Izvršeno - - - - TorConfigurationPage - - - Does this computer need a proxy to access the internet? - Potrebuje ta računalnik proksi za dostop do medmrežja? - - - - Proxy type: - Vrsta proksija: - - - - - None - Brez - - - - Address: - Naslov - - - - IP address or hostname - Naslov IP ali ime gostitelja - - - - Port: - Vrata: - - - - Username: - Uporabniško ime: - - - - - Optional - Izbirno - - - - Password: - Geslo: - - - - Does this computer's Internet connection go through a firewall that only allows connections to certain ports? - Ali poteka povezava tega računalnika z medmrežjem čez požarni zid, ki dovoljuje povezavo le skozi določena vrata? - - - - Allowed ports: - Dovoljena vrata: - - - - Example: 80,443 - Primer: 80,443 - - - - If this computer's Internet connection is censored, you will need to obtain and use bridge relays. - Če je povezava tega računalnika z medmrežjem cenzurirana, morate pridobiti posredniške mostove - bridge relays. - - - - Enter one or more bridge relays (one per line): - Vnesite enega ali več posredniških mostov (enega na vrstico): - - - - Back - Nazaj - - - - Connect - Poveži se - - - - TorPreferences - - - Running: - Teče: - - - - - - Yes - Da - - - - - - No - Ne - - - - External - Zunanje - - - - Control connected: - Nadzorno povezan: - - - - Circuits established: - Vzpostavljen krog: - - - - Hidden service: - Skrite storitve: - - - - Online - Povezan - - - - Offline - Brez povezave - - - - Version: - Različica: - - - - Error: <b>%1</b> - %1 is error message - Napaka: <b>%1</b> - - - - Configure - Nastavi - - - - TorStateWidget - - - - Connection failed - Povezava ni uspela - - - - - Connecting… - \u2026 is ellipsis - Povezujem... - - - - Connecting… (%1%) - %1 is progress percentage, e.g. 100 - Povezujem… (%1%) - - - - Online - Povezan - - - - Connected - Povezan - - - diff --git a/translation/ricochet_sq.ts b/translation/ricochet_sq.ts deleted file mode 100644 index d155e1a7..00000000 --- a/translation/ricochet_sq.ts +++ /dev/null @@ -1,701 +0,0 @@ - - - - - AboutPreferences - - - Ricochet %1 - %1 version, e.g. 1.0.0 - Ricochet %1 - - - - AddContactDialog - - - Share your Ricochet ID to allow connection requests - Ndani me të tjerët ID-në tuaj Ricochet që të lejoni kërkesa lidhjesh - - - - Cancel - Anulojeni - - - - Add - Shtoje - - - - ContactActions - - - Open Window - Hap Dritare - - - - Details... - Hollësi… - - - - Rename - Riemërtojeni - - - - Remove - Hiqe - - - - ContactIDField - - - <b>%1</b> is already your contact - <b>%1</b> është tashmë kontakt i juaji - - - - You can't add yourself as a contact - S’mund të shtoni veten si kontakt - - - - Enter an ID starting with <b>ricochet:</b> - Jepni një ID që fillon me <b>ricochet:</b> - - - - Copied to clipboard - U kopjua në të papastër - - - - Copy - Kopjoje - - - - ContactList - - - Online - Në linjë - - - - Offline - Jo në linjë - - - - Requests - Kërkesa - - - - Rejected - E hedhur tej - - - - Outdated - E vjetruar - - - - ContactPreferences - - - Date added: - Datë shtimi: - - - - Last seen: - Parë së fundi më: - - - - Request: - Kërkesë: - - - - Pending connection - Lidhje në pritje të pranimit - - - - Delivered - Të dërguar - - - - Accepted - Të pranuara - - - - Error - Gabim - - - - Rejected - E hedhur tej - - - - %1 (Connected) - %1 status, e.g. "Accepted" - %1 (I lidhur) - - - - Response: - Përgjigje: - - - - Rename - Riemërtojeni - - - - Remove - Hiqe - - - - ContactRequestDialog - - - Someone new is asking to connect to you - Dikush i ri po kërkon të lidhet me ju - - - - Reject - Hidheni tej - - - - Accept - Pranojeni - - - - ContactRequestFields - - - ID: - ID: - - - - Name: - Emër: - - - - Message: - Mesazh: - - - - GeneralPreferences - - - Use a single window for conversations - Përdor një dritare të vetme për bisedat - - - - Open links in default browser without prompting - Hapi lidhjet në shfletuesin parazgjedhje, pa pyetur - - - - Play audio notifications - Luaji njoftimet audion - - - - Volume - Volum - - - - Language - Gjuhë - - - - Restart Ricochet to apply changes - Riniseni Ricochet-in që të zbatohen ndryshimet - - - - LanguagePreferences - - Select Language - Përzgjidhni Gjuhë - - - Restart Ricochet to apply changes - Riniseni Ricochet-in që të zbatohen ndryshimet - - - - LanguagesModel - - - System default - Parazgjedhje sistemi - - - - Main - - - Ricochet Error - Gabim Ricochet-i - - - - MainToolBar - - - Add Contact - Shtoni Kontakt - - - - Preferences - Parapëlqime - - - - Click to add contacts - Klikoni që të shtoni kontakte - - - - MainWindow - - - Remove %1 - Hiqe - - - - Do you want to permanently remove %1? - Doni të hiqet %1 përgjithmonë? - - - - MessageDelegate - - - %1 is offline - %1 nickname - %1 s’është në linjë - - - - Copy ID - Kopjo ID-në - - - - Copy Link - Kopjo Lidhjen - - - - Open with Browser - Hape me Shfletues - - - - Add as Contact - Shtojeni si Kontakt - - - - Copy Message - Kopjo Mesazhin - - - - Copy Selection - Kopjo Përzgjedhjen - - - - MessageDialogWrapper - - - Remove %1 - Hiqe %1 - - - - Do you want to permanently remove %1? - %1 nickname - Doni të hiqet %1 përgjithmonë? - - - - This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. - Ky kontakt s’do të jetë më në gjendje t’ju dërgojë mesazhe, dhe do të njoftohet për heqjen. Ai mund të zgjedhë t’ju dërgojë një kërkesë të re lidhjeje. - - - - NetworkSetupWizard - - - This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. - Lidhja në Internet e këtij kompjuteri është pa pengesa. Do të doja të lidhem drejtpërsëdrejti me rrjetin Tor. - - - - Connect - Lidhu - - - - This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. - Lidhja në Internet e këtij kompjuteri është e censuruar, e filtruar ose bëhet përmes një ndërmjetësi. Kam nevojë të formësoj rregullime rrjeti. - - - - Configure - Formësojeni - - - - OfflineStateItem - - - Configure - Formësojeni - - - - Details - Hollësi - - - - Connection failed - Lidhja dështoi - - - - Connecting… - \u2026 is ellipsis - Po lidhet… - - - - OpenBrowserDialog - - - <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. - <b>Kujdes!</b> Hapja e lidhjeve me shfletuesin tuaj parazgjedhje do të rrezikojë sigurinë dhe anonimitetin tuaj.<br><br>Më mirë mund <a href='.'>ta kopjoni në të papastër</a>. - - - - Don't ask again for links from %1 - Mos pyet sërish për lidhje nga %1 - - - - Don't ask again for any links (not recommended!) - Mos pyet më, për çfarëdo lidhje (e pakëshillueshme!) - - - - Open Browser - Hap Shfletues - - - - Cancel - Anulojeni - - - - PreferencesDialog - - - Ricochet Preferences - Parapëlqime për Ricochet-in - - - - General - Të përgjithshme - - - Language - Gjuhë - - - - Contacts - Kontakte - - - - Tor - Tor - - - - About - Mbi - - - - QCocoaMenuItem - - - Preference - Parapëlqim - - - - StartupStatusPage - - - The Tor process was not started successfully. This is most likely an installation or system error. - Procesi Tor s’u nis me sukses. Ka shumë të ngjarë që ky të jetë një gabim instalimi ose sistemi. - - - - Quit - Dilni - - - - TorBootstrapStatus - - - Connecting to the Tor network… - \u2026 is ellipsis - Po lidhet me rrjetin TOR… - - - - Back - Mbrapsht - - - - Hide details - Fshihi hollësitë - - - - Show details - Shfaqi hollësitë - - - - Done - U bë - - - - TorConfigurationPage - - - Does this computer need a proxy to access the internet? - Ka nevojë për ndërmjetës ky kompjuter, që të lidhet në internet? - - - - Proxy type: - Lloj ndërmjetësi: - - - - - None - Asnjë - - - - Address: - Adresë: - - - - IP address or hostname - Adresë IP ose emër strehe - - - - Port: - Portë: - - - - Username: - Emër përdoruesi: - - - - - Optional - Opsionale - - - - Password: - Fjalëkalim: - - - - Does this computer's Internet connection go through a firewall that only allows connections to certain ports? - A kalon përmes një firewall-i lidhja internet e këtij kompjuteri, i cili lejon lidhje vetëm te disa prej portave? - - - - Allowed ports: - Porta të lejuara: - - - - Example: 80,443 - Shembull: 80,443 - - - - If this computer's Internet connection is censored, you will need to obtain and use bridge relays. - Nëse lidhja në Internet e këtij kompjuteri është e censuruar, do t’ju duhet të merrni dhe përdorni <i>bridge relays</i>. - - - - Enter one or more bridge relays (one per line): - Jepni një ose më tepër <i>bridge relays</i> (një për rresht): - - - - Back - Mbrapsht - - - - Connect - Lidhu - - - - TorPreferences - - - Running: - Xhiron: - - - - - - Yes - Po - - - - - - No - Jo - - - - External - I jashtëm - - - - Control connected: - I lidhur me kontrollin: - - - - Circuits established: - Qarqe të vendosur: - - - - Hidden service: - Shërbim i fshehur: - - - - Online - Në linjë - - - - Offline - Jo në linjë - - - - Version: - Version: - - - - Error: <b>%1</b> - %1 is error message - Gabim: <b>%1</b> - - - - Configure - Formësojeni - - - - TorStateWidget - - - - Connection failed - Lidhja dështoi - - - - - Connecting… - \u2026 is ellipsis - Po lidhet… - - - - Connecting… (%1%) - %1 is progress percentage, e.g. 100 - Po lidhet… (%1%) - - - - Online - Në linjë - - - - Connected - I lidhur - - - diff --git a/translation/ricochet_sv.ts b/translation/ricochet_sv.ts deleted file mode 100644 index abbd656a..00000000 --- a/translation/ricochet_sv.ts +++ /dev/null @@ -1,701 +0,0 @@ - - - - - AboutPreferences - - - Ricochet %1 - %1 version, e.g. 1.0.0 - Ricochet %1 - - - - AddContactDialog - - - Share your Ricochet ID to allow connection requests - Dela ditt Ricochet-ID så att du kan ta emot förfrågningar - - - - Cancel - Avbryt - - - - Add - Lägg till - - - - ContactActions - - - Open Window - Öppna fönster - - - - Details... - Detaljer... - - - - Rename - Ändra namn - - - - Remove - Ta bort - - - - ContactIDField - - - <b>%1</b> is already your contact - <b>%1</b> finns redan bland dina kontakter - - - - You can't add yourself as a contact - Du kan inte lägga till dig själv som kontakt - - - - Enter an ID starting with <b>ricochet:</b> - Fyll i ett ID som startar med <b>ricochet:</b> - - - - Copied to clipboard - Kopierat till Urklipp - - - - Copy - Kopiera - - - - ContactList - - - Online - Online - - - - Offline - Offline - - - - Requests - Förfrågningar - - - - Rejected - Nekad - - - - Outdated - Inaktuell - - - - ContactPreferences - - - Date added: - Tillagd datum: - - - - Last seen: - Sågs senast: - - - - Request: - Förfrågan: - - - - Pending connection - Inväntar anslutning - - - - Delivered - Levererad - - - - Accepted - Godkänd - - - - Error - Fel - - - - Rejected - Nekad - - - - %1 (Connected) - %1 status, e.g. "Accepted" - %1 (Ansluten) - - - - Response: - Svar: - - - - Rename - Ändra namn - - - - Remove - Ta bort - - - - ContactRequestDialog - - - Someone new is asking to connect to you - Någon ber att få kontakt med dig - - - - Reject - Avböj - - - - Accept - Acceptera - - - - ContactRequestFields - - - ID: - ID: - - - - Name: - Namn: - - - - Message: - Meddelande: - - - - GeneralPreferences - - - Use a single window for conversations - Använd bara ett fönster för konversationer - - - - Open links in default browser without prompting - Öppna länkar i standardwebbläsaren utan att fråga - - - - Play audio notifications - Spela ljudnotiser - - - - Volume - Volym - - - - Language - Språk - - - - Restart Ricochet to apply changes - Starta om Ricochet för att aktivera ändringarna - - - - LanguagePreferences - - Select Language - Välj språk - - - Restart Ricochet to apply changes - Starta om Ricochet för att aktivera ändringarna - - - - LanguagesModel - - - System default - Systemets standardval - - - - Main - - - Ricochet Error - Ricochet fel - - - - MainToolBar - - - Add Contact - Lägg till kontakt - - - - Preferences - Inställningar - - - - Click to add contacts - Klicka för att lägga till kontakter - - - - MainWindow - - - Remove %1 - Ta bort %1 - - - - Do you want to permanently remove %1? - Vill du ta bort %1 permanent? - - - - MessageDelegate - - - %1 is offline - %1 nickname - %1 är offline - - - - Copy ID - Kopiera ID - - - - Copy Link - Kopiera länk - - - - Open with Browser - Öppna med webbläsare - - - - Add as Contact - Lägg till som kontakt - - - - Copy Message - Kopiera meddelande - - - - Copy Selection - Kopiera markering - - - - MessageDialogWrapper - - - Remove %1 - Ta bort %1 - - - - Do you want to permanently remove %1? - %1 nickname - Vill du ta bort %1 permanent? - - - - This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. - Den här kontakten kommer inte längre att kunna skicka meddelanden till dig, och kommer att bli informerad om borttagningen. Kontakten kan välja att skicka en ny förfrågan. - - - - NetworkSetupWizard - - - This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. - Den här datorn kan ansluta till Internet obehindrat. Jag vill ansluta direkt till Tor-nätverket. - - - - Connect - Anslut - - - - This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. - Den här datorns Internetanslutning är censurerad, filtrerad eller kräver användning av en proxy. Jag behöver konfigurera nätverksinställningarna. - - - - Configure - Konfigurera - - - - OfflineStateItem - - - Configure - Konfigurera - - - - Details - Detaljer - - - - Connection failed - Anslutning misslyckades - - - - Connecting… - \u2026 is ellipsis - Ansluter... - - - - OpenBrowserDialog - - - <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. - <b>Varning!</b> Att öppna länkar med din standard&shy;webbläsare skadar din säkerhet och anonymitet.<br><br>Du kan <a href='.'>kopiera till Urklipp</a> istället. - - - - Don't ask again for links from %1 - Fråga inte igen för länkar från %1 - - - - Don't ask again for any links (not recommended!) - Fråga inte igen för några länkar (rekommenderas inte!) - - - - Open Browser - Öppna webbläsaren - - - - Cancel - Avbryt - - - - PreferencesDialog - - - Ricochet Preferences - Inställningar för Ricochet - - - - General - Allmänt - - - Language - Språk - - - - Contacts - Kontakter - - - - Tor - Tor - - - - About - Om - - - - QCocoaMenuItem - - - Preference - Inställning - - - - StartupStatusPage - - - The Tor process was not started successfully. This is most likely an installation or system error. - Tor-processen startade inte korrekt. Det beror förmodligen på ett installations- eller systemfel. - - - - Quit - Avsluta - - - - TorBootstrapStatus - - - Connecting to the Tor network… - \u2026 is ellipsis - Ansluter till Tor-nätverket... - - - - Back - Tillbaka - - - - Hide details - Göm detaljer - - - - Show details - Visa detaljer - - - - Done - Klar - - - - TorConfigurationPage - - - Does this computer need a proxy to access the internet? - Behöver den här datorn använda en proxy för att ansluta till Internet? - - - - Proxy type: - Typ av proxy: - - - - - None - Ingen - - - - Address: - Adress: - - - - IP address or hostname - IP-adress eller värdnamn - - - - Port: - Port: - - - - Username: - Användarnamn: - - - - - Optional - Krävs ej - - - - Password: - Lösenord: - - - - Does this computer's Internet connection go through a firewall that only allows connections to certain ports? - Går den här datorns Internetanslutning genom en brandvägg som bara tillåter anslutningar på vissa portar? - - - - Allowed ports: - Tillåtna portar: - - - - Example: 80,443 - Exempel: 80,443 - - - - If this computer's Internet connection is censored, you will need to obtain and use bridge relays. - Om den här datorns Internetanslutning är censurerad måste du skaffa och använda en brygga till Tor, en så kallad ”bridge relay”. - - - - Enter one or more bridge relays (one per line): - Ange en eller flera bryggor (en per rad): - - - - Back - Tillbaka - - - - Connect - Anslut - - - - TorPreferences - - - Running: - Körs: - - - - - - Yes - Ja - - - - - - No - Nej - - - - External - Extern - - - - Control connected: - Kontroll ansluten: - - - - Circuits established: - Kretsar etablerade: - - - - Hidden service: - Dold tjänst: - - - - Online - Online - - - - Offline - Offline - - - - Version: - Version: - - - - Error: <b>%1</b> - %1 is error message - Fel: <b>%1</b> - - - - Configure - Konfigurera - - - - TorStateWidget - - - - Connection failed - Anslutning misslyckades - - - - - Connecting… - \u2026 is ellipsis - Ansluter... - - - - Connecting… (%1%) - %1 is progress percentage, e.g. 100 - Ansluter… (%1%) - - - - Online - Online - - - - Connected - Ansluten - - - diff --git a/translation/ricochet_tr.ts b/translation/ricochet_tr.ts deleted file mode 100644 index 5ced85fe..00000000 --- a/translation/ricochet_tr.ts +++ /dev/null @@ -1,701 +0,0 @@ - - - - - AboutPreferences - - - Ricochet %1 - %1 version, e.g. 1.0.0 - Ricochet %1 - - - - AddContactDialog - - - Share your Ricochet ID to allow connection requests - Bağlantı isteklerine izin vermek için Ricochet kimliğinizi paylaşın - - - - Cancel - İptal - - - - Add - Ekle - - - - ContactActions - - - Open Window - Yeni Pencere - - - - Details... - Ayrıntılar... - - - - Rename - Yeniden adlandır - - - - Remove - Çıkar - - - - ContactIDField - - - <b>%1</b> is already your contact - <b>%1</b> kişi listenize zaten eklenmiş - - - - You can't add yourself as a contact - Kendinizi kişi listesine ekleyemezsiniz - - - - Enter an ID starting with <b>ricochet:</b> - <b>ricochet:</b> ile başlayan bir kimlik girin - - - - Copied to clipboard - Panoya kopyalandı - - - - Copy - Kopyala - - - - ContactList - - - Online - Çevrimiçi - - - - Offline - Çevrimdışı - - - - Requests - İstekler - - - - Rejected - Reddedildi - - - - Outdated - Güncel Değil - - - - ContactPreferences - - - Date added: - Eklenme tarihi: - - - - Last seen: - Son görülme tarihi: - - - - Request: - İstek: - - - - Pending connection - Beklemedeki bağlantı - - - - Delivered - Ulaştırıldı - - - - Accepted - Kabul edildi - - - - Error - Hata - - - - Rejected - Reddedildi - - - - %1 (Connected) - %1 status, e.g. "Accepted" - %1 (Bağlandı) - - - - Response: - Cevap: - - - - Rename - Yeniden adlandır - - - - Remove - Çıkar - - - - ContactRequestDialog - - - Someone new is asking to connect to you - Yeni bir kişi sizinle bağlantı kurmak istiyor - - - - Reject - Reddet - - - - Accept - Kabul et - - - - ContactRequestFields - - - ID: - Kimlik: - - - - Name: - Ad: - - - - Message: - Mesaj: - - - - GeneralPreferences - - - Use a single window for conversations - Görüşmeler için tek pencere kullan - - - - Open links in default browser without prompting - Bağlantıları bana sormadan varsayılan tarayıcıda aç - - - - Play audio notifications - Ses bildirimlerini oynat - - - - Volume - Ses - - - - Language - Dil - - - - Restart Ricochet to apply changes - Değişikliklerin uygulanması için Ricochet'i yeniden başlatın. - - - - LanguagePreferences - - Select Language - Dil Seçin - - - Restart Ricochet to apply changes - Değişikliklerin uygulanması için Ricochet'i yeniden başlatın. - - - - LanguagesModel - - - System default - Sistem varsayılanı - - - - Main - - - Ricochet Error - Ricochet Hatası - - - - MainToolBar - - - Add Contact - Kişi Ekle - - - - Preferences - Ayarlar - - - - Click to add contacts - Kişi eklemek için tıklayın - - - - MainWindow - - - Remove %1 - %1 isimli kullanıcıyı kişi listesinden çıkar - - - - Do you want to permanently remove %1? - %1 isimli kullanıcıyı kalıcı olarak silmek istiyor musunuz? - - - - MessageDelegate - - - %1 is offline - %1 nickname - %1 çevrimdışı - - - - Copy ID - Kimliği kopyala - - - - Copy Link - Bağlantı Kopyala - - - - Open with Browser - Tarayıcı ile Aç - - - - Add as Contact - Kişi olarak Ekle - - - - Copy Message - Mesajı Kopyala - - - - Copy Selection - Seçileni Kopyala - - - - MessageDialogWrapper - - - Remove %1 - %1 isimli kullanıcıyı sil - - - - Do you want to permanently remove %1? - %1 nickname - %1 isimli kullanıcıyı kalıcı olarak silmek istiyor musunuz? - - - - This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. - Bu kişi artık size mesaj gönderemeyecek ve silinme işlemiyle ilgili bilgilendirilecek. Kişi isterse size yeni bir bağlantı isteği gönderebilir. - - - - NetworkSetupWizard - - - This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. - Bu bilgisayarın internete bağlanmasında bir engel yoktur. Doğrudan Tor ağına bağlanmak istiyorum. - - - - Connect - Bağlan - - - - This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. - Bu bilgisayarın internet bağlantısı sansürlenmiş, filtrelenmiş ya da proxy kullanmaktadır. Ağ ayarlarını yapılandırmam gerekiyor. - - - - Configure - Yapılandır - - - - OfflineStateItem - - - Configure - Yapılandır - - - - Details - Ayrıntılar - - - - Connection failed - Bağlantı başarısız - - - - Connecting… - \u2026 is ellipsis - Bağlanıyor... - - - - OpenBrowserDialog - - - <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. - <b>Uyarı!</b> Varsayılan tarayıcı ile bağlantıları açmak, güvenlik ve anonimliğinize zarar verecektir.<br><br>Bunun yerine <a href='.'>panoya kopyala</a>yabilirsiniz. - - - - Don't ask again for links from %1 - %1 isimli kullanıcıdan gelen bağlantılar için tekrar sorma - - - - Don't ask again for any links (not recommended!) - Bundan sonra bağlantılar için bir daha sorma (tavsiye edilmez!) - - - - Open Browser - Tarayıcıyı Aç - - - - Cancel - İptal - - - - PreferencesDialog - - - Ricochet Preferences - Ricochet Ayarları - - - - General - Genel - - - Language - Dil - - - - Contacts - Kişiler - - - - Tor - Tor - - - - About - Hakkında - - - - QCocoaMenuItem - - - Preference - Tercih - - - - StartupStatusPage - - - The Tor process was not started successfully. This is most likely an installation or system error. - Tor başlatılamadı. Büyük olasılıkla kurulum veya sistem hatası. - - - - Quit - Çık - - - - TorBootstrapStatus - - - Connecting to the Tor network… - \u2026 is ellipsis - Tor ağına bağlanıyor... - - - - Back - Geri - - - - Hide details - Ayrıntıları gizle - - - - Show details - Ayrıntıları göster - - - - Done - Tamamla - - - - TorConfigurationPage - - - Does this computer need a proxy to access the internet? - Bu bilgisayar internete erişmek için bir proxy'e ihtiyaç duyuyor mu? - - - - Proxy type: - Proxy türü: - - - - - None - Hiçbiri - - - - Address: - Adres: - - - - IP address or hostname - IP adresi veya host adı - - - - Port: - Port: - - - - Username: - Kullanıcı adı: - - - - - Optional - İsteğe bağlı - - - - Password: - Parola: - - - - Does this computer's Internet connection go through a firewall that only allows connections to certain ports? - Bu bilgisayarın internet bağlantısı sadece belli portlara bağlantı izni veren bir güvenlik duvarı üzerinden gidiyor mu? - - - - Allowed ports: - İzin verilen portlar: - - - - Example: 80,443 - Örnek: 80.443 - - - - If this computer's Internet connection is censored, you will need to obtain and use bridge relays. - Bu bilgisayarın internet bağlantısı sansürlenmişse, köprü aktarımlarını edinmeniz ve kullanmanız gerekir. - - - - Enter one or more bridge relays (one per line): - Bir veya birden fazla köprü aktarım adresi girin (satır başına bir tane): - - - - Back - Geri - - - - Connect - Bağlan - - - - TorPreferences - - - Running: - Çalışıyor: - - - - - - Yes - Evet - - - - - - No - Hayır - - - - External - Harici - - - - Control connected: - Kontrol bağlantısı: - - - - Circuits established: - Devrelere bağlanıldı: - - - - Hidden service: - Gizli servis: - - - - Online - Çevrimiçi - - - - Offline - Çevrimdışı - - - - Version: - Sürüm: - - - - Error: <b>%1</b> - %1 is error message - Hata: <b>%1</b> - - - - Configure - Yapılandır - - - - TorStateWidget - - - - Connection failed - Bağlantı başarısız - - - - - Connecting… - \u2026 is ellipsis - Bağlanıyor... - - - - Connecting… (%1%) - %1 is progress percentage, e.g. 100 - Bağlanıyor... (%1%) - - - - Online - Çevrimiçi - - - - Connected - Bağlı - - - diff --git a/translation/ricochet_uk.ts b/translation/ricochet_uk.ts deleted file mode 100644 index 82287288..00000000 --- a/translation/ricochet_uk.ts +++ /dev/null @@ -1,701 +0,0 @@ - - - - - AboutPreferences - - - Ricochet %1 - %1 version, e.g. 1.0.0 - Ricochet %1 - - - - AddContactDialog - - - Share your Ricochet ID to allow connection requests - Поділитись вашим Ricochet ID для запитів на підключення - - - - Cancel - Відмінити - - - - Add - Додати - - - - ContactActions - - - Open Window - Відкрити вікно - - - - Details... - Детальніше... - - - - Rename - Перейменувати - - - - Remove - Видалити - - - - ContactIDField - - - <b>%1</b> is already your contact - <b>%1</b> це вже ваш контакт - - - - You can't add yourself as a contact - Ви не можете додати самі себе як контакт - - - - Enter an ID starting with <b>ricochet:</b> - Введіть ID який починається з <b>ricochet:</b> - - - - Copied to clipboard - Скопійовано до буферу обміну - - - - Copy - Копіювати - - - - ContactList - - - Online - В мережі - - - - Offline - Не в мережі - - - - Requests - Запити - - - - Rejected - Відхилено - - - - Outdated - Застарілий - - - - ContactPreferences - - - Date added: - Дата додавання: - - - - Last seen: - Останній раз бачили: - - - - Request: - Запит: - - - - Pending connection - Встановлення з'єднання - - - - Delivered - Доставлено - - - - Accepted - Прийнято - - - - Error - Помилка - - - - Rejected - Відхилено - - - - %1 (Connected) - %1 status, e.g. "Accepted" - %1 (Підключено) - - - - Response: - Відповідь: - - - - Rename - Перейменувати - - - - Remove - Видалити - - - - ContactRequestDialog - - - Someone new is asking to connect to you - Хтось новий запрошує підключення до вас - - - - Reject - Відхилити - - - - Accept - Прийняти - - - - ContactRequestFields - - - ID: - ID: - - - - Name: - Ім'я: - - - - Message: - Повідомлення: - - - - GeneralPreferences - - - Use a single window for conversations - Використовувати одне вікно для розмов - - - - Open links in default browser without prompting - Відкривати посилання в браузері без запитань - - - - Play audio notifications - Програти аудіо повідомлення - - - - Volume - Гучність - - - - Language - Мова - - - - Restart Ricochet to apply changes - Перезавантажити Ricochet для збереження змін - - - - LanguagePreferences - - Select Language - Вибір мови - - - Restart Ricochet to apply changes - Перезавантажити Ricochet для збереження змін - - - - LanguagesModel - - - System default - Системні налаштування - - - - Main - - - Ricochet Error - Помилка Ricochet - - - - MainToolBar - - - Add Contact - Додати контакт - - - - Preferences - Налаштування - - - - Click to add contacts - Натисніть щоб додати контакти - - - - MainWindow - - - Remove %1 - Видалити %1 - - - - Do you want to permanently remove %1? - Бажаєте назавжди видалити %1? - - - - MessageDelegate - - - %1 is offline - %1 nickname - %1 не в мережі - - - - Copy ID - Копіювати ID - - - - Copy Link - Копіювати посилання - - - - Open with Browser - Відкрити браузер - - - - Add as Contact - Додати як контакт - - - - Copy Message - Скопіювати повідомлення - - - - Copy Selection - Скопіювати вибране - - - - MessageDialogWrapper - - - Remove %1 - Видалити %1 - - - - Do you want to permanently remove %1? - %1 nickname - Бажаєте назавжди видалити %1? - - - - This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. - Цей контакт більше не зможе вам писати і буде проінформований про видалення. Він може послати запит на нове з'єднання. - - - - NetworkSetupWizard - - - This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. - Це інтернет з'єднання не блокується провайдером. Я б хотів підключитись напряму до мережі Tor. - - - - Connect - Підключення - - - - This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. - Це інтернет з'єднання цензурується провайдером або фільтрується. Мені треба налаштувати параметри мережі. - - - - Configure - Налаштувати - - - - OfflineStateItem - - - Configure - Налаштувати - - - - Details - Детальніше - - - - Connection failed - З'єднання не вдалося. - - - - Connecting… - \u2026 is ellipsis - З'єднання... - - - - OpenBrowserDialog - - - <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. - <b>Попередження!</b> Відкриття посилань у браузері за замовчуванням може нашкодити вашій безпеці та анонімності. <br><br>Замість цього ви можете їх <a href='.'>скопіювати у буфер обміну</a> . - - - - Don't ask again for links from %1 - Не запитувати знову про посилання від %1 - - - - Don't ask again for any links (not recommended!) - Не запитувати знову про будь-які посилання (не рекомендовано!) - - - - Open Browser - Відкрити браузер - - - - Cancel - Відмінити - - - - PreferencesDialog - - - Ricochet Preferences - Налаштування Ricochet - - - - General - Основні - - - Language - Мова - - - - Contacts - Контакти - - - - Tor - Tor - - - - About - Про - - - - QCocoaMenuItem - - - Preference - Налаштування - - - - StartupStatusPage - - - The Tor process was not started successfully. This is most likely an installation or system error. - Процес Tor не був вдало запущений. Скоріш за все це помилка при установці або системна. - - - - Quit - Вийти - - - - TorBootstrapStatus - - - Connecting to the Tor network… - \u2026 is ellipsis - З'єднання з мережею Tor... - - - - Back - Назад - - - - Hide details - Сховати деталі - - - - Show details - Показати деталі - - - - Done - Виконано - - - - TorConfigurationPage - - - Does this computer need a proxy to access the internet? - Чи потребує цей комп'ютер проксі для доступу до інтернету? - - - - Proxy type: - Тип проксі: - - - - - None - Ніякого - - - - Address: - Адреса: - - - - IP address or hostname - IP адреса чи ім'я сервера - - - - Port: - Порт: - - - - Username: - Ім'я: - - - - - Optional - Опції - - - - Password: - Пароль: - - - - Does this computer's Internet connection go through a firewall that only allows connections to certain ports? - З'єднання цього комп'ютеру проходять через firewall який дозволяй з'єднання тільки з певними портами? - - - - Allowed ports: - Дозволені порти: - - - - Example: 80,443 - Наприклад: 80,443 - - - - If this computer's Internet connection is censored, you will need to obtain and use bridge relays. - Якщо інтернет з'єднання цього комп'ютеру цензуруються, то вам потрібно отримати і використовувати ретранслятори. - - - - Enter one or more bridge relays (one per line): - Введіть один або декілька адрес ретрансляторів (один на лінію) - - - - Back - Назад - - - - Connect - Підключення - - - - TorPreferences - - - Running: - Запущено: - - - - - - Yes - Так - - - - - - No - Ні - - - - External - Зовнішній - - - - Control connected: - Контрольне з'єднання: - - - - Circuits established: - Схема побудована: - - - - Hidden service: - Прихований сервіс: - - - - Online - В мережі - - - - Offline - Не в мережі - - - - Version: - Версія: - - - - Error: <b>%1</b> - %1 is error message - Помилка: <b>%1</b> - - - - Configure - Налаштувати - - - - TorStateWidget - - - - Connection failed - З'єднання не вдалося. - - - - - Connecting… - \u2026 is ellipsis - З'єднання... - - - - Connecting… (%1%) - %1 is progress percentage, e.g. 100 - З'єднання... (%1%) - - - - Online - В мережі - - - - Connected - Підключено - - - diff --git a/translation/ricochet_zh.ts b/translation/ricochet_zh.ts deleted file mode 100644 index b7da4d14..00000000 --- a/translation/ricochet_zh.ts +++ /dev/null @@ -1,701 +0,0 @@ - - - - - AboutPreferences - - - Ricochet %1 - %1 version, e.g. 1.0.0 - Ricochet %1 - - - - AddContactDialog - - - Share your Ricochet ID to allow connection requests - 分享您的Ricochet ID给你的好友 - - - - Cancel - 取消 - - - - Add - 添加 - - - - ContactActions - - - Open Window - 打开窗口 - - - - Details... - 细节…… - - - - Rename - 重命名 - - - - Remove - 移除 - - - - ContactIDField - - - <b>%1</b> is already your contact - <b>%1</b>已经是你的联系人 - - - - You can't add yourself as a contact - 不能添加自己为联系人 - - - - Enter an ID starting with <b>ricochet:</b> - 输入一个ID并以<b>ricochet:</b>打头 - - - - Copied to clipboard - 复制到剪贴板 - - - - Copy - 复制 - - - - ContactList - - - Online - 在线 - - - - Offline - 离线 - - - - Requests - 请求 - - - - Rejected - 已拒绝 - - - - Outdated - - - - - ContactPreferences - - - Date added: - 添加日期: - - - - Last seen: - 最后上线: - - - - Request: - 请求: - - - - Pending connection - 挂起的连接 - - - - Delivered - 送达 - - - - Accepted - 已接受 - - - - Error - 错误 - - - - Rejected - 已拒绝 - - - - %1 (Connected) - %1 status, e.g. "Accepted" - %1 (已连接) - - - - Response: - 回复: - - - - Rename - 重命名 - - - - Remove - 移除 - - - - ContactRequestDialog - - - Someone new is asking to connect to you - 有陌生人希望连接你 - - - - Reject - 拒绝 - - - - Accept - 接受 - - - - ContactRequestFields - - - ID: - 帐号: - - - - Name: - 名字: - - - - Message: - 消息: - - - - GeneralPreferences - - - Use a single window for conversations - - - - - Open links in default browser without prompting - 无需提示,直接在默认浏览器中打开链接 - - - - Play audio notifications - 使用声音提醒 - - - - Volume - 音量 - - - - Language - 语言 - - - - Restart Ricochet to apply changes - 应用并重启 - - - - LanguagePreferences - - Select Language - 选择语言 - - - Restart Ricochet to apply changes - 应用并重启 - - - - LanguagesModel - - - System default - 系统默认 - - - - Main - - - Ricochet Error - Ricochet错误 - - - - MainToolBar - - - Add Contact - 添加联系人 - - - - Preferences - 选项 - - - - Click to add contacts - 点击添加联系人 - - - - MainWindow - - - Remove %1 - 移除%1 - - - - Do you want to permanently remove %1? - 确定永久移除%1? - - - - MessageDelegate - - - %1 is offline - %1 nickname - %1离线中 - - - - Copy ID - 复制帐号 - - - - Copy Link - 复制链接 - - - - Open with Browser - 用浏览器打开 - - - - Add as Contact - 添加为联系人 - - - - Copy Message - 复制消息 - - - - Copy Selection - 复制选择内容 - - - - MessageDialogWrapper - - - Remove %1 - 移除%1 - - - - Do you want to permanently remove %1? - %1 nickname - 确定永久移除%1? - - - - This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. - - - - - NetworkSetupWizard - - - This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. - 这台电脑的网络连接没有任何障碍,我希望直接连到Tor网络。 - - - - Connect - 连接 - - - - This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. - 这台电脑的网络连接是经过审查、过滤或代理的,我希望配置网络设置。 - - - - Configure - 配置 - - - - OfflineStateItem - - - Configure - 配置 - - - - Details - 细节 - - - - Connection failed - 连接失败 - - - - Connecting… - \u2026 is ellipsis - 连接中…… - - - - OpenBrowserDialog - - - <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. - - - - - Don't ask again for links from %1 - 不再询问来自%1的链接 - - - - Don't ask again for any links (not recommended!) - 不再询问任何链接(不推荐) - - - - Open Browser - 打开浏览器 - - - - Cancel - 取消 - - - - PreferencesDialog - - - Ricochet Preferences - Ricochet选项 - - - - General - 通用 - - - Language - 语言 - - - - Contacts - 联系人 - - - - Tor - Tor - - - - About - 关于 - - - - QCocoaMenuItem - - - Preference - 选项 - - - - StartupStatusPage - - - The Tor process was not started successfully. This is most likely an installation or system error. - Tor 没有启动成功。可能需要重新安装。 - - - - Quit - 退出 - - - - TorBootstrapStatus - - - Connecting to the Tor network… - \u2026 is ellipsis - 连接到Tor网络中…… - - - - Back - 返回 - - - - Hide details - 隐藏细节 - - - - Show details - 显示细节 - - - - Done - 已完成 - - - - TorConfigurationPage - - - Does this computer need a proxy to access the internet? - 这台电脑需要通过代理来连接网络吗? - - - - Proxy type: - 代理类型: - - - - - None - None - - - - Address: - 地址: - - - - IP address or hostname - IP地址或主机名 - - - - Port: - 端口: - - - - Username: - 用户名: - - - - - Optional - 可选 - - - - Password: - 密码: - - - - Does this computer's Internet connection go through a firewall that only allows connections to certain ports? - - - - - Allowed ports: - 允许的端口: - - - - Example: 80,443 - 例如:80,443 - - - - If this computer's Internet connection is censored, you will need to obtain and use bridge relays. - - - - - Enter one or more bridge relays (one per line): - - - - - Back - 返回 - - - - Connect - 连接 - - - - TorPreferences - - - Running: - 运行中 - - - - - - Yes - - - - - - - No - - - - - External - 外部 - - - - Control connected: - 联系人已连接: - - - - Circuits established: - 线路已经建立: - - - - Hidden service: - 隐藏的服务: - - - - Online - 在线 - - - - Offline - 离线 - - - - Version: - 版本: - - - - Error: <b>%1</b> - %1 is error message - 错误: <b>%1</b> - - - - Configure - 配置 - - - - TorStateWidget - - - - Connection failed - 连接失败 - - - - - Connecting… - \u2026 is ellipsis - 连接中…… - - - - Connecting… (%1%) - %1 is progress percentage, e.g. 100 - 连接中……(%1%) - - - - Online - 在线 - - - - Connected - 已连接 - - - diff --git a/translation/ricochet_zh_HK.ts b/translation/ricochet_zh_HK.ts deleted file mode 100644 index e763451e..00000000 --- a/translation/ricochet_zh_HK.ts +++ /dev/null @@ -1,701 +0,0 @@ - - - - - AboutPreferences - - - Ricochet %1 - %1 version, e.g. 1.0.0 - Ricochet %1 - - - - AddContactDialog - - - Share your Ricochet ID to allow connection requests - 分享你的Ricochet ID 以允許連線的請求 - - - - Cancel - 取消 - - - - Add - 新增 - - - - ContactActions - - - Open Window - 開啟視窗 - - - - Details... - 細節 - - - - Rename - 重新命名 - - - - Remove - 移除 - - - - ContactIDField - - - <b>%1</b> is already your contact - <b>%1</b> 已是你的聯絡人 - - - - You can't add yourself as a contact - 你不能新增自己為聯絡人 - - - - Enter an ID starting with <b>ricochet:</b> - 輸入ID開始使用 <b>ricochet:</b> - - - - Copied to clipboard - 複製到剪貼簿 - - - - Copy - 複製 - - - - ContactList - - - Online - 上線 - - - - Offline - 離線 - - - - Requests - 請求 - - - - Rejected - 拒絕 - - - - Outdated - 過時 - - - - ContactPreferences - - - Date added: - 新增之日期: - - - - Last seen: - 上回瀏覧: - - - - Request: - 請求: - - - - Pending connection - 連線擱置中 - - - - Delivered - 已傳送 - - - - Accepted - 已接受 - - - - Error - 錯誤 - - - - Rejected - 拒絕 - - - - %1 (Connected) - %1 status, e.g. "Accepted" - %1 (己連結) - - - - Response: - 回應: - - - - Rename - 重新命名 - - - - Remove - 移除 - - - - ContactRequestDialog - - - Someone new is asking to connect to you - 某新人要求與你連線 - - - - Reject - 拒絕 - - - - Accept - 接受 - - - - ContactRequestFields - - - ID: - ID: - - - - Name: - 名稱: - - - - Message: - 訊息: - - - - GeneralPreferences - - - Use a single window for conversations - 使用單一視窗來對話 - - - - Open links in default browser without prompting - 在原瀏覧器中開啟連結而不要跳出新視窗 - - - - Play audio notifications - 開啟通知音訊 - - - - Volume - 音量 - - - - Language - 語言 - - - - Restart Ricochet to apply changes - 重啟Ricochet 以更新改變 - - - - LanguagePreferences - - Select Language - 選取語言 - - - Restart Ricochet to apply changes - 重啟Ricochet 以更新改變 - - - - LanguagesModel - - - System default - 系統預設 - - - - Main - - - Ricochet Error - Ricochet 出錯 - - - - MainToolBar - - - Add Contact - 新增聯絡人 - - - - Preferences - 偏好 - - - - Click to add contacts - 點擊來新增聯絡人 - - - - MainWindow - - - Remove %1 - 移除%1 - - - - Do you want to permanently remove %1? - 你是否要永遠移除%1? - - - - MessageDelegate - - - %1 is offline - %1 nickname - %1 已離線 - - - - Copy ID - 複製ID - - - - Copy Link - 複製連結 - - - - Open with Browser - 開啟瀏覧器 - - - - Add as Contact - 新增聯絡人 - - - - Copy Message - 複製訊息 - - - - Copy Selection - 複製選項 - - - - MessageDialogWrapper - - - Remove %1 - 移除%1 - - - - Do you want to permanently remove %1? - %1 nickname - 你是否要永遠移除%1? - - - - This contact will no longer be able to message you, and will be notified about the removal. They may choose to send a new connection request. - 這名聯絡人不再傳訊給你,也將接到遭移除的通知。他們可以選擇重新送出連結請求 - - - - NetworkSetupWizard - - - This computer's Internet connection is free of obstacles. I would like to connect directly to the Tor network. - 這台電腦的網路連結未有任何阻礙,可以直接連結上Tor網路 - - - - Connect - 連線 - - - - This computer's Internet connection is censored, filtered, or proxied. I need to configure network settings. - 如果這台電腦的網路連線遭到監控,我必須修改網路設定 - - - - Configure - 設定 - - - - OfflineStateItem - - - Configure - 設定 - - - - Details - 細節 - - - - Connection failed - 連線失敗 - - - - Connecting… - \u2026 is ellipsis - 連線中 - - - - OpenBrowserDialog - - - <b>Warning!</b> Opening links with your default browser will harm your security and anonymity.<br><br>You can <a href='.'>copy to the clipboard</a> instead. - <b>警告!</b> 用你的瀏覧器直接開啟連結可能會對安全與匿名性造成威脅.<br><br>你可另以<a href='.'>複製到剪貼簿</a>取代 . - - - - Don't ask again for links from %1 - 別再請求來自%1的連結 - - - - Don't ask again for any links (not recommended!) - 別再請求任何連結(不建議!) - - - - Open Browser - 開啟瀏覧器 - - - - Cancel - 取消 - - - - PreferencesDialog - - - Ricochet Preferences - Ricochet 偏好 - - - - General - 一般 - - - Language - 語言 - - - - Contacts - 聯絡人 - - - - Tor - Tor - - - - About - 關於 - - - - QCocoaMenuItem - - - Preference - 偏好 - - - - StartupStatusPage - - - The Tor process was not started successfully. This is most likely an installation or system error. - Tor連結過程並未成功啟動,這大部份是由於安裝或系統出錯 - - - - Quit - 中止 - - - - TorBootstrapStatus - - - Connecting to the Tor network… - \u2026 is ellipsis - 連線到Tor網路 - - - - Back - 退後 - - - - Hide details - 隱藏細節 - - - - Show details - 顯示細節 - - - - Done - 完成 - - - - TorConfigurationPage - - - Does this computer need a proxy to access the internet? - 這台電腦是否需要透過代理主機來連網? - - - - Proxy type: - Proxy 類型: - - - - - None - - - - - Address: - 地址: - - - - IP address or hostname - I位置或主機名稱 - - - - Port: - 埠部: - - - - Username: - 使用者名稱: - - - - - Optional - 選項 - - - - Password: - 密碼: - - - - Does this computer's Internet connection go through a firewall that only allows connections to certain ports? - 這台電腦的網路連線是否透過防火牆,只能充許某些埠號來連網? - - - - Allowed ports: - 允許埠部: - - - - Example: 80,443 - 示範: 80,443 - - - - If this computer's Internet connection is censored, you will need to obtain and use bridge relays. - 如果這台電腦的網路連線遭到監控,你必須改用中續撟接服務 - - - - Enter one or more bridge relays (one per line): - 輸入一個以上的中續橋接(一行一個): - - - - Back - 退後 - - - - Connect - 連線 - - - - TorPreferences - - - Running: - 執行中: - - - - - - Yes - - - - - - - No - - - - - External - 外部 - - - - Control connected: - 連線控制: - - - - Circuits established: - 已建立巡迴: - - - - Hidden service: - 隱藏服務: - - - - Online - 上線 - - - - Offline - 離線 - - - - Version: - 版本: - - - - Error: <b>%1</b> - %1 is error message - <b>%1</b>出錯 - - - - Configure - 設定 - - - - TorStateWidget - - - - Connection failed - 連線失敗 - - - - - Connecting… - \u2026 is ellipsis - 連線中 - - - - Connecting… (%1%) - %1 is progress percentage, e.g. 100 - (%1%)連線中… - - - - Online - 上線 - - - - Connected - 已連結 - - -