dino-0.1.0/0000755000000000000000000000000013614354364011150 5ustar rootrootdino-0.1.0/.gitmodules0000644000000000000000000000025313614354364013325 0ustar rootroot[submodule "libsignal-protocol-c"] path = plugins/signal-protocol/libsignal-protocol-c url = https://github.com/WhisperSystems/libsignal-protocol-c.git branch = v2.3.2 dino-0.1.0/CMakeLists.txt0000644000000000000000000002153113614354364013712 0ustar rootrootcmake_minimum_required(VERSION 3.3) list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) include(ComputeVersion) if (NOT VERSION_FOUND) project(Dino LANGUAGES C) elseif (VERSION_IS_RELEASE) project(Dino VERSION ${VERSION_FULL} LANGUAGES C) else () project(Dino LANGUAGES C) set(PROJECT_VERSION ${VERSION_FULL}) endif () # Prepare Plugins set(DEFAULT_PLUGINS omemo;openpgp;http-files) foreach (plugin ${DEFAULT_PLUGINS}) if ("$CACHE{DINO_PLUGIN_ENABLED_${plugin}}" STREQUAL "") if (NOT DEFINED DINO_PLUGIN_ENABLED_${plugin}}) set(DINO_PLUGIN_ENABLED_${plugin} "yes" CACHE BOOL "Enable plugin ${plugin}") else () set(DINO_PLUGIN_ENABLED_${plugin} "${DINO_PLUGIN_ENABLED_${plugin}}" CACHE BOOL "Enable plugin ${plugin}" FORCE) endif () if (DINO_PLUGIN_ENABLED_${plugin}) message(STATUS "Enabled plugin: ${plugin}") else () message(STATUS "Disabled plugin: ${plugin}") endif () endif () endforeach (plugin) if (DISABLED_PLUGINS) foreach(plugin ${DISABLED_PLUGINS}) set(DINO_PLUGIN_ENABLED_${plugin} "no" CACHE BOOL "Enable plugin ${plugin}" FORCE) message(STATUS "Disabled plugin: ${plugin}") endforeach(plugin) endif (DISABLED_PLUGINS) if (ENABLED_PLUGINS) foreach(plugin ${ENABLED_PLUGINS}) set(DINO_PLUGIN_ENABLED_${plugin} "yes" CACHE BOOL "Enable plugin ${plugin}" FORCE) message(STATUS "Enabled plugin: ${plugin}") endforeach(plugin) endif (ENABLED_PLUGINS) set(PLUGINS "") get_cmake_property(all_variables VARIABLES) foreach (variable_name ${all_variables}) if (variable_name MATCHES "^DINO_PLUGIN_ENABLED_(.+)$" AND ${variable_name}) list(APPEND PLUGINS ${CMAKE_MATCH_1}) endif() endforeach () list(SORT PLUGINS) string(REPLACE ";" ", " PLUGINS_TEXT "${PLUGINS}") message(STATUS "Configuring Dino ${PROJECT_VERSION} with plugins: ${PLUGINS_TEXT}") # Prepare instal paths macro(set_path what val desc) if (NOT ${what}) unset(${what} CACHE) set(${what} ${val}) endif () if (NOT "${${what}}" STREQUAL "${_${what}_SET}") message(STATUS "${desc}: ${${what}}") set(_${what}_SET ${${what}} CACHE INTERNAL ${desc}) endif() endmacro(set_path) string(REGEX REPLACE "^liblib" "lib" LIBDIR_NAME "lib${LIB_SUFFIX}") set_path(CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}" "Installation directory for architecture-independent files") set_path(EXEC_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}" "Installation directory for architecture-dependent files") set_path(SHARE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}/share" "Installation directory for read-only architecture-independent data") set_path(BIN_INSTALL_DIR "${EXEC_INSTALL_PREFIX}/bin" "Installation directory for user executables") set_path(DATA_INSTALL_DIR "${SHARE_INSTALL_PREFIX}/dino" "Installation directory for dino-specific data") set_path(APPDATA_FILE_INSTALL_DIR "${SHARE_INSTALL_PREFIX}/metainfo" "Installation directory for .appdata.xml files") set_path(DESKTOP_FILE_INSTALL_DIR "${SHARE_INSTALL_PREFIX}/applications" "Installation directory for .desktop files") set_path(SERVICE_FILE_INSTALL_DIR "${SHARE_INSTALL_PREFIX}/dbus-1/services" "Installation directory for .service files") set_path(ICON_INSTALL_DIR "${SHARE_INSTALL_PREFIX}/icons" "Installation directory for icons") set_path(INCLUDE_INSTALL_DIR "${EXEC_INSTALL_PREFIX}/include" "Installation directory for C header files") set_path(LIB_INSTALL_DIR "${EXEC_INSTALL_PREFIX}/${LIBDIR_NAME}" "Installation directory for object code libraries") set_path(LOCALE_INSTALL_DIR "${SHARE_INSTALL_PREFIX}/locale" "Installation directory for locale files") set_path(PLUGIN_INSTALL_DIR "${LIB_INSTALL_DIR}/dino/plugins" "Installation directory for dino plugin object code files") set_path(VAPI_INSTALL_DIR "${SHARE_INSTALL_PREFIX}/vala/vapi" "Installation directory for Vala API files") set(TARGET_INSTALL LIBRARY DESTINATION ${LIB_INSTALL_DIR} RUNTIME DESTINATION ${BIN_INSTALL_DIR} PUBLIC_HEADER DESTINATION ${INCLUDE_INSTALL_DIR} ARCHIVE DESTINATION ${LIB_INSTALL_DIR}) set(PLUGIN_INSTALL LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} RUNTIME DESTINATION ${PLUGIN_INSTALL_DIR}) include(CheckCCompilerFlag) include(CheckCSourceCompiles) macro(AddCFlagIfSupported list flag) string(REGEX REPLACE "[^a-z^A-Z^_^0-9]+" "_" flag_name ${flag}) check_c_compiler_flag(${flag} COMPILER_SUPPORTS${flag_name}) if (${COMPILER_SUPPORTS${flag_name}}) set(${list} "${${list}} ${flag}") endif () endmacro() if ("Ninja" STREQUAL ${CMAKE_GENERATOR}) AddCFlagIfSupported(CMAKE_C_FLAGS -fdiagnostics-color) endif () # Flags for all C files AddCFlagIfSupported(CMAKE_C_FLAGS -Wall) AddCFlagIfSupported(CMAKE_C_FLAGS -Wextra) AddCFlagIfSupported(CMAKE_C_FLAGS -Werror=format-security) AddCFlagIfSupported(CMAKE_C_FLAGS -Wno-duplicate-decl-specifier) if (NOT VALA_WARN) set(VALA_WARN "conversion") endif () set(VALA_WARN "${VALA_WARN}" CACHE STRING "Which warnings to show when invoking C compiler on Vala compiler output") set_property(CACHE VALA_WARN PROPERTY STRINGS "all;unused;qualifier;conversion;deprecated;format;none") # Vala generates some unused stuff if (NOT ("all" IN_LIST VALA_WARN OR "unused" IN_LIST VALA_WARN)) AddCFlagIfSupported(VALA_CFLAGS -Wno-unused-but-set-variable) AddCFlagIfSupported(VALA_CFLAGS -Wno-unused-function) AddCFlagIfSupported(VALA_CFLAGS -Wno-unused-label) AddCFlagIfSupported(VALA_CFLAGS -Wno-unused-parameter) AddCFlagIfSupported(VALA_CFLAGS -Wno-unused-value) AddCFlagIfSupported(VALA_CFLAGS -Wno-unused-variable) endif () if (NOT ("all" IN_LIST VALA_WARN OR "qualifier" IN_LIST VALA_WARN)) AddCFlagIfSupported(VALA_CFLAGS -Wno-discarded-qualifiers) AddCFlagIfSupported(VALA_CFLAGS -Wno-discarded-array-qualifiers) endif () if (NOT ("all" IN_LIST VALA_WARN OR "deprecated" IN_LIST VALA_WARN)) AddCFlagIfSupported(VALA_CFLAGS -Wno-deprecated-declarations) endif () if (NOT ("all" IN_LIST VALA_WARN OR "format" IN_LIST VALA_WARN)) AddCFlagIfSupported(VALA_CFLAGS -Wno-missing-braces) endif () if (NOT ("all" IN_LIST VALA_WARN OR "conversion" IN_LIST VALA_WARN)) AddCFlagIfSupported(VALA_CFLAGS -Wno-int-conversion) AddCFlagIfSupported(VALA_CFLAGS -Wno-pointer-sign) AddCFlagIfSupported(VALA_CFLAGS -Wno-incompatible-pointer-types) endif () try_compile(__WITHOUT_FILE_OFFSET_BITS_64 ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/cmake/LargeFileOffsets.c COMPILE_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS}) if (NOT __WITHOUT_FILE_OFFSET_BITS_64) try_compile(__WITH_FILE_OFFSET_BITS_64 ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/cmake/LargeFileOffsets.c COMPILE_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS} -D_FILE_OFFSET_BITS=64) if (__WITH_FILE_OFFSET_BITS_64) AddCFlagIfSupported(CMAKE_C_FLAGS -D_FILE_OFFSET_BITS=64) message(STATUS "Enabled large file support using _FILE_OFFSET_BITS=64") else (__WITH_FILE_OFFSET_BITS_64) message(STATUS "Large file support not available") endif (__WITH_FILE_OFFSET_BITS_64) unset(__WITH_FILE_OFFSET_BITS_64) endif (NOT __WITHOUT_FILE_OFFSET_BITS_64) unset(__WITHOUT_FILE_OFFSET_BITS_64) if ($ENV{USE_CCACHE}) # Configure CCache if available find_program(CCACHE_BIN ccache) mark_as_advanced(CCACHE_BIN) if (CCACHE_BIN) message(STATUS "Using ccache") set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CCACHE_BIN}) set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ${CCACHE_BIN}) else (CCACHE_BIN) message(STATUS "USE_CCACHE was set but ccache was not found") endif (CCACHE_BIN) endif ($ENV{USE_CCACHE}) if (NOT NO_DEBUG) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g") set(CMAKE_VALA_FLAGS "${CMAKE_VALA_FLAGS} -g") endif (NOT NO_DEBUG) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) set(GTK3_GLOBAL_VERSION 3.22) set(GLib_GLOBAL_VERSION 2.38) set(ICU_GLOBAL_VERSION 57) if (NOT VALA_EXECUTABLE) unset(VALA_EXECUTABLE CACHE) endif () find_package(Vala 0.34 REQUIRED) if (VALA_VERSION VERSION_GREATER "0.34.90" AND VALA_VERSION VERSION_LESS "0.36.1" OR # Due to a bug on 0.36.0 (and pre-releases), we need to disable FAST_VAPI VALA_VERSION VERSION_EQUAL "0.44.10" OR VALA_VERSION VERSION_EQUAL "0.46.4" OR VALA_VERSION VERSION_EQUAL "0.47.1") # See Dino issue #646 set(DISABLE_FAST_VAPI yes) endif () include(${VALA_USE_FILE}) include(MultiFind) include(GlibCompileResourcesSupport) set(CMAKE_VALA_FLAGS "${CMAKE_VALA_FLAGS} --target-glib=${GLib_GLOBAL_VERSION}") add_subdirectory(qlite) add_subdirectory(xmpp-vala) add_subdirectory(libdino) add_subdirectory(main) add_subdirectory(plugins) # uninstall target configure_file("${CMAKE_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in" "${CMAKE_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY) add_custom_target(uninstall COMMAND ${CMAKE_COMMAND} -P ${CMAKE_BINARY_DIR}/cmake_uninstall.cmake COMMENT "Uninstall the project...") dino-0.1.0/LICENSE0000644000000000000000000010451213614354364012160 0ustar rootroot GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read .dino-0.1.0/README.md0000644000000000000000000000317113614354364012431 0ustar rootroot![Dino](https://dino.im/img/readme_header.svg) ======= ![screenshots](https://dino.im/img/screenshot-main.png) Installation ------------ Have a look at the [prebuilt packages](https://github.com/dino/dino/wiki/Distribution-Packages). Build ----- Make sure to install all [dependencies](https://github.com/dino/dino/wiki/Build#dependencies). ./configure make build/dino Resources --------- - Check out the [Dino website](https://dino.im). - Join our XMPP conference room at `chat@dino.im`. - The [wiki](https://github.com/dino/dino/wiki) provides additional information. Contribute ---------- - Pull requests are welcome. Please discuss bigger changes in the conference room first. - Look at [how to debug](https://github.com/dino/dino/wiki/Debugging) Dino before you report a bug. - Help [translating](https://hosted.weblate.org/projects/dino/) Dino into your language. License ------- Dino - Modern Jabber/XMPP Client using GTK+/Vala Copyright (C) 2016-2020 Dino contributors This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . dino-0.1.0/VERSION0000644000000000000000000000001613614354364012215 0ustar rootrootRELEASE 0.1.0 dino-0.1.0/cmake/0000755000000000000000000000000013614354364012230 5ustar rootrootdino-0.1.0/cmake/BuildTargetScript.cmake0000644000000000000000000000444313614354364016632 0ustar rootroot# This file is used to be invoked at build time. It generates the needed # resource XML file. # Input variables that need to provided when invoking this script: # GXML_OUTPUT The output file path where to save the XML file. # GXML_COMPRESS_ALL Sets all COMPRESS flags in all resources in resource # list. # GXML_NO_COMPRESS_ALL Removes all COMPRESS flags in all resources in # resource list. # GXML_STRIPBLANKS_ALL Sets all STRIPBLANKS flags in all resources in # resource list. # GXML_NO_STRIPBLANKS_ALL Removes all STRIPBLANKS flags in all resources in # resource list. # GXML_TOPIXDATA_ALL Sets all TOPIXDATA flags i nall resources in resource # list. # GXML_NO_TOPIXDATA_ALL Removes all TOPIXDATA flags in all resources in # resource list. # GXML_PREFIX Overrides the resource prefix that is prepended to # each relative name in registered resources. # GXML_RESOURCES The list of resource files. Whether absolute or # relative path is equal. # Include the GENERATE_GXML() function. include(${CMAKE_CURRENT_LIST_DIR}/GenerateGXML.cmake) # Set flags to actual invocation flags. if(GXML_COMPRESS_ALL) set(GXML_COMPRESS_ALL COMPRESS_ALL) endif() if(GXML_NO_COMPRESS_ALL) set(GXML_NO_COMPRESS_ALL NO_COMPRESS_ALL) endif() if(GXML_STRIPBLANKS_ALL) set(GXML_STRIPBLANKS_ALL STRIPBLANKS_ALL) endif() if(GXML_NO_STRIPBLANKS_ALL) set(GXML_NO_STRIPBLANKS_ALL NO_STRIPBLANKS_ALL) endif() if(GXML_TOPIXDATA_ALL) set(GXML_TOPIXDATA_ALL TOPIXDATA_ALL) endif() if(GXML_NO_TOPIXDATA_ALL) set(GXML_NO_TOPIXDATA_ALL NO_TOPIXDATA_ALL) endif() # Replace " " with ";" to import the list over the command line. Otherwise # CMake would interprete the passed resources as a whole string. string(REPLACE " " ";" GXML_RESOURCES ${GXML_RESOURCES}) # Invoke the gresource XML generation function. generate_gxml(${GXML_OUTPUT} ${GXML_COMPRESS_ALL} ${GXML_NO_COMPRESS_ALL} ${GXML_STRIPBLANKS_ALL} ${GXML_NO_STRIPBLANKS_ALL} ${GXML_TOPIXDATA_ALL} ${GXML_NO_TOPIXDATA_ALL} PREFIX ${GXML_PREFIX} RESOURCES ${GXML_RESOURCES}) dino-0.1.0/cmake/CompileGResources.cmake0000644000000000000000000002404413614354364016630 0ustar rootrootinclude(CMakeParseArguments) # Path to this file. set(GCR_CMAKE_MACRO_DIR ${CMAKE_CURRENT_LIST_DIR}) # Compiles a gresource resource file from given resource files. Automatically # creates the XML controlling file. # The type of resource to generate (header, c-file or bundle) is automatically # determined from TARGET file ending, if no TYPE is explicitly specified. # The output file is stored in the provided variable "output". # "xml_out" contains the variable where to output the XML path. Can be used to # create custom targets or doing postprocessing. # If you want to use preprocessing, you need to manually check the existence # of the tools you use. This function doesn't check this for you, it just # generates the XML file. glib-compile-resources will then throw a # warning/error. function(COMPILE_GRESOURCES output xml_out) # Available options: # COMPRESS_ALL, NO_COMPRESS_ALL Overrides the COMPRESS flag in all # registered resources. # STRIPBLANKS_ALL, NO_STRIPBLANKS_ALL Overrides the STRIPBLANKS flag in all # registered resources. # TOPIXDATA_ALL, NO_TOPIXDATA_ALL Overrides the TOPIXDATA flag in all # registered resources. set(CG_OPTIONS COMPRESS_ALL NO_COMPRESS_ALL STRIPBLANKS_ALL NO_STRIPBLANKS_ALL TOPIXDATA_ALL NO_TOPIXDATA_ALL) # Available one value options: # TYPE Type of resource to create. Valid options are: # EMBED_C: A C-file that can be compiled with your project. # EMBED_H: A header that can be included into your project. # BUNDLE: Generates a resource bundle file that can be loaded # at runtime. # AUTO: Determine from target file ending. Need to specify # target argument. # PREFIX Overrides the resource prefix that is prepended to each # relative file name in registered resources. # SOURCE_DIR Overrides the resources base directory to search for resources. # Normally this is set to the source directory with that CMake # was invoked (CMAKE_SOURCE_DIR). # TARGET Overrides the name of the output file/-s. Normally the output # names from glib-compile-resources tool is taken. set(CG_ONEVALUEARGS TYPE PREFIX SOURCE_DIR TARGET) # Available multi-value options: # RESOURCES The list of resource files. Whether absolute or relative path is # equal, absolute paths are stripped down to relative ones. If the # absolute path is not inside the given base directory SOURCE_DIR # or CMAKE_SOURCE_DIR (if SOURCE_DIR is not overriden), this # function aborts. # OPTIONS Extra command line options passed to glib-compile-resources. set(CG_MULTIVALUEARGS RESOURCES OPTIONS) # Parse the arguments. cmake_parse_arguments(CG_ARG "${CG_OPTIONS}" "${CG_ONEVALUEARGS}" "${CG_MULTIVALUEARGS}" "${ARGN}") # Variable to store the double-quote (") string. Since escaping # double-quotes in strings is not possible we need a helper variable that # does this job for us. set(Q \") # Check invocation validity with the _UNPARSED_ARGUMENTS variable. # If other not recognized parameters were passed, throw error. if (CG_ARG_UNPARSED_ARGUMENTS) set(CG_WARNMSG "Invocation of COMPILE_GRESOURCES with unrecognized") set(CG_WARNMSG "${CG_WARNMSG} parameters. Parameters are:") set(CG_WARNMSG "${CG_WARNMSG} ${CG_ARG_UNPARSED_ARGUMENTS}.") message(WARNING ${CG_WARNMSG}) endif() # Check invocation validity depending on generation mode (EMBED_C, EMBED_H # or BUNDLE). if ("${CG_ARG_TYPE}" STREQUAL "EMBED_C") # EMBED_C mode, output compilable C-file. set(CG_GENERATE_COMMAND_LINE "--generate-source") set(CG_TARGET_FILE_ENDING "c") elseif ("${CG_ARG_TYPE}" STREQUAL "EMBED_H") # EMBED_H mode, output includable header file. set(CG_GENERATE_COMMAND_LINE "--generate-header") set(CG_TARGET_FILE_ENDING "h") elseif ("${CG_ARG_TYPE}" STREQUAL "BUNDLE") # BUNDLE mode, output resource bundle. Don't do anything since # glib-compile-resources outputs a bundle when not specifying # something else. set(CG_TARGET_FILE_ENDING "gresource") else() # Everything else is AUTO mode, determine from target file ending. if (CG_ARG_TARGET) set(CG_GENERATE_COMMAND_LINE "--generate") else() set(CG_ERRMSG "AUTO mode given, but no target specified. Can't") set(CG_ERRMSG "${CG_ERRMSG} determine output type. In function") set(CG_ERRMSG "${CG_ERRMSG} COMPILE_GRESOURCES.") message(FATAL_ERROR ${CG_ERRMSG}) endif() endif() # Check flag validity. if (CG_ARG_COMPRESS_ALL AND CG_ARG_NO_COMPRESS_ALL) set(CG_ERRMSG "COMPRESS_ALL and NO_COMPRESS_ALL simultaneously set. In") set(CG_ERRMSG "${CG_ERRMSG} function COMPILE_GRESOURCES.") message(FATAL_ERROR ${CG_ERRMSG}) endif() if (CG_ARG_STRIPBLANKS_ALL AND CG_ARG_NO_STRIPBLANKS_ALL) set(CG_ERRMSG "STRIPBLANKS_ALL and NO_STRIPBLANKS_ALL simultaneously") set(CG_ERRMSG "${CG_ERRMSG} set. In function COMPILE_GRESOURCES.") message(FATAL_ERROR ${CG_ERRMSG}) endif() if (CG_ARG_TOPIXDATA_ALL AND CG_ARG_NO_TOPIXDATA_ALL) set(CG_ERRMSG "TOPIXDATA_ALL and NO_TOPIXDATA_ALL simultaneously set.") set(CG_ERRMSG "${CG_ERRMSG} In function COMPILE_GRESOURCES.") message(FATAL_ERROR ${CG_ERRMSG}) endif() # Check if there are any resources. if (NOT CG_ARG_RESOURCES) set(CG_ERRMSG "No resource files to process. In function") set(CG_ERRMSG "${CG_ERRMSG} COMPILE_GRESOURCES.") message(FATAL_ERROR ${CG_ERRMSG}) endif() # Extract all dependencies for targets from resource list. foreach(res ${CG_ARG_RESOURCES}) if (NOT(("${res}" STREQUAL "COMPRESS") OR ("${res}" STREQUAL "STRIPBLANKS") OR ("${res}" STREQUAL "TOPIXDATA"))) add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/resources/${res}" COMMAND ${CMAKE_COMMAND} -E copy "${CG_ARG_SOURCE_DIR}/${res}" "${CMAKE_CURRENT_BINARY_DIR}/resources/${res}" MAIN_DEPENDENCY "${CG_ARG_SOURCE_DIR}/${res}") list(APPEND CG_RESOURCES_DEPENDENCIES "${CMAKE_CURRENT_BINARY_DIR}/resources/${res}") endif() endforeach() # Construct .gresource.xml path. set(CG_XML_FILE_PATH "${CMAKE_CURRENT_BINARY_DIR}/resources/.gresource.xml") # Generate gresources XML target. list(APPEND CG_CMAKE_SCRIPT_ARGS "-D") list(APPEND CG_CMAKE_SCRIPT_ARGS "GXML_OUTPUT=${Q}${CG_XML_FILE_PATH}${Q}") if(CG_ARG_COMPRESS_ALL) list(APPEND CG_CMAKE_SCRIPT_ARGS "-D") list(APPEND CG_CMAKE_SCRIPT_ARGS "GXML_COMPRESS_ALL") endif() if(CG_ARG_NO_COMPRESS_ALL) list(APPEND CG_CMAKE_SCRIPT_ARGS "-D") list(APPEND CG_CMAKE_SCRIPT_ARGS "GXML_NO_COMPRESS_ALL") endif() if(CG_ARG_STRPIBLANKS_ALL) list(APPEND CG_CMAKE_SCRIPT_ARGS "-D") list(APPEND CG_CMAKE_SCRIPT_ARGS "GXML_STRIPBLANKS_ALL") endif() if(CG_ARG_NO_STRIPBLANKS_ALL) list(APPEND CG_CMAKE_SCRIPT_ARGS "-D") list(APPEND CG_CMAKE_SCRIPT_ARGS "GXML_NO_STRIPBLANKS_ALL") endif() if(CG_ARG_TOPIXDATA_ALL) list(APPEND CG_CMAKE_SCRIPT_ARGS "-D") list(APPEND CG_CMAKE_SCRIPT_ARGS "GXML_TOPIXDATA_ALL") endif() if(CG_ARG_NO_TOPIXDATA_ALL) list(APPEND CG_CMAKE_SCRIPT_ARGS "-D") list(APPEND CG_CMAKE_SCRIPT_ARGS "GXML_NO_TOPIXDATA_ALL") endif() list(APPEND CG_CMAKE_SCRIPT_ARGS "-D") list(APPEND CG_CMAKE_SCRIPT_ARGS "GXML_PREFIX=${Q}${CG_ARG_PREFIX}${Q}") list(APPEND CG_CMAKE_SCRIPT_ARGS "-D") list(APPEND CG_CMAKE_SCRIPT_ARGS "GXML_RESOURCES=${Q}${CG_ARG_RESOURCES}${Q}") list(APPEND CG_CMAKE_SCRIPT_ARGS "-P") list(APPEND CG_CMAKE_SCRIPT_ARGS "${Q}${GCR_CMAKE_MACRO_DIR}/BuildTargetScript.cmake${Q}") get_filename_component(CG_XML_FILE_PATH_ONLY_NAME "${CG_XML_FILE_PATH}" NAME) set(CG_XML_CUSTOM_COMMAND_COMMENT "Creating gresources XML file (${CG_XML_FILE_PATH_ONLY_NAME})") add_custom_command(OUTPUT ${CG_XML_FILE_PATH} COMMAND ${CMAKE_COMMAND} ARGS ${CG_CMAKE_SCRIPT_ARGS} DEPENDS ${CG_RESOURCES_DEPENDENCIES} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMENT ${CG_XML_CUSTOM_COMMAND_COMMENT}) # Create target manually if not set (to make sure glib-compile-resources # doesn't change behaviour with it's naming standards). if (NOT CG_ARG_TARGET) set(CG_ARG_TARGET "${CMAKE_CURRENT_BINARY_DIR}/resources") set(CG_ARG_TARGET "${CG_ARG_TARGET}.${CG_TARGET_FILE_ENDING}") endif() # Create source directory automatically if not set. if (NOT CG_ARG_SOURCE_DIR) set(CG_ARG_SOURCE_DIR "${CMAKE_SOURCE_DIR}") endif() # Add compilation target for resources. add_custom_command(OUTPUT ${CG_ARG_TARGET} COMMAND ${GLIB_COMPILE_RESOURCES_EXECUTABLE} ARGS ${OPTIONS} "--target=${Q}${CG_ARG_TARGET}${Q}" "--sourcedir=${Q}${CG_ARG_SOURCE_DIR}${Q}" ${CG_GENERATE_COMMAND_LINE} ${CG_XML_FILE_PATH} MAIN_DEPENDENCY ${CG_XML_FILE_PATH} DEPENDS ${CG_RESOURCES_DEPENDENCIES} WORKING_DIRECTORY ${CMAKE_BUILD_DIR}) # Set output and XML_OUT to parent scope. set(${xml_out} ${CG_XML_FILE_PATH} PARENT_SCOPE) set(${output} ${CG_ARG_TARGET} PARENT_SCOPE) endfunction() dino-0.1.0/cmake/ComputeVersion.cmake0000644000000000000000000000753713614354364016230 0ustar rootrootinclude(CMakeParseArguments) function(_compute_version_from_file) set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/VERSION) if (NOT EXISTS ${CMAKE_SOURCE_DIR}/VERSION) set(VERSION_FOUND 0 PARENT_SCOPE) return() endif () file(STRINGS ${CMAKE_SOURCE_DIR}/VERSION VERSION_FILE) string(REPLACE " " ";" VERSION_FILE "${VERSION_FILE}") cmake_parse_arguments(VERSION_FILE "" "RELEASE;PRERELEASE" "" ${VERSION_FILE}) if (DEFINED VERSION_FILE_RELEASE) string(STRIP "${VERSION_FILE_RELEASE}" VERSION_FILE_RELEASE) set(VERSION_IS_RELEASE 1 PARENT_SCOPE) set(VERSION_FULL "${VERSION_FILE_RELEASE}" PARENT_SCOPE) set(VERSION_FOUND 1 PARENT_SCOPE) elseif (DEFINED VERSION_FILE_PRERELEASE) string(STRIP "${VERSION_FILE_PRERELEASE}" VERSION_FILE_PRERELEASE) set(VERSION_IS_RELEASE 0 PARENT_SCOPE) set(VERSION_FULL "${VERSION_FILE_PRERELEASE}" PARENT_SCOPE) set(VERSION_FOUND 1 PARENT_SCOPE) else () set(VERSION_FOUND 0 PARENT_SCOPE) endif () endfunction(_compute_version_from_file) function(_compute_version_from_git) set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/.git) if (NOT GIT_EXECUTABLE) find_package(Git QUIET) if (NOT GIT_FOUND) return() endif () endif (NOT GIT_EXECUTABLE) # Git tag execute_process( COMMAND "${GIT_EXECUTABLE}" describe --tags --abbrev=0 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" RESULT_VARIABLE git_result OUTPUT_VARIABLE git_tag ERROR_VARIABLE git_error OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_STRIP_TRAILING_WHITESPACE ) if (NOT git_result EQUAL 0) return() endif (NOT git_result EQUAL 0) if (git_tag MATCHES "^v?([0-9]+[.]?[0-9]*[.]?[0-9]*)(-[.0-9A-Za-z-]+)?([+][.0-9A-Za-z-]+)?$") set(VERSION_LAST_RELEASE "${CMAKE_MATCH_1}") else () return() endif () # Git describe execute_process( COMMAND "${GIT_EXECUTABLE}" describe --tags WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" RESULT_VARIABLE git_result OUTPUT_VARIABLE git_describe ERROR_VARIABLE git_error OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_STRIP_TRAILING_WHITESPACE ) if (NOT git_result EQUAL 0) return() endif (NOT git_result EQUAL 0) if ("${git_tag}" STREQUAL "${git_describe}") set(VERSION_IS_RELEASE 1) else () set(VERSION_IS_RELEASE 0) if (git_describe MATCHES "-([0-9]+)-g([0-9a-f]+)$") set(VERSION_TAG_OFFSET "${CMAKE_MATCH_1}") set(VERSION_COMMIT_HASH "${CMAKE_MATCH_2}") endif () execute_process( COMMAND "${GIT_EXECUTABLE}" show --format=%cd --date=format:%Y%m%d -s WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" RESULT_VARIABLE git_result OUTPUT_VARIABLE git_time ERROR_VARIABLE git_error OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_STRIP_TRAILING_WHITESPACE ) if (NOT git_result EQUAL 0) return() endif (NOT git_result EQUAL 0) set(VERSION_COMMIT_DATE "${git_time}") endif () if (NOT VERSION_IS_RELEASE) set(VERSION_SUFFIX "~git${VERSION_TAG_OFFSET}.${VERSION_COMMIT_DATE}.${VERSION_COMMIT_HASH}") else (NOT VERSION_IS_RELEASE) set(VERSION_SUFFIX "") endif (NOT VERSION_IS_RELEASE) set(VERSION_IS_RELEASE ${VERSION_IS_RELEASE} PARENT_SCOPE) set(VERSION_FULL "${VERSION_LAST_RELEASE}${VERSION_SUFFIX}" PARENT_SCOPE) set(VERSION_FOUND 1 PARENT_SCOPE) endfunction(_compute_version_from_git) _compute_version_from_file() if (NOT VERSION_FOUND) _compute_version_from_git() endif (NOT VERSION_FOUND)dino-0.1.0/cmake/FindATK.cmake0000644000000000000000000000267013614354364014457 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(ATK PKG_CONFIG_NAME atk LIB_NAMES atk-1.0 INCLUDE_NAMES atk/atk.h INCLUDE_DIR_SUFFIXES atk-1.0 atk-1.0/include DEPENDS GObject ) if(ATK_FOUND AND NOT ATK_VERSION) find_file(ATK_VERSION_HEADER "atk/atkversion.h" HINTS ${ATK_INCLUDE_DIRS}) mark_as_advanced(ATK_VERSION_HEADER) if(ATK_VERSION_HEADER) file(STRINGS "${ATK_VERSION_HEADER}" ATK_MAJOR_VERSION REGEX "^#define ATK_MAJOR_VERSION +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define ATK_MAJOR_VERSION \\(?([0-9]+)\\)?$" "\\1" ATK_MAJOR_VERSION "${ATK_MAJOR_VERSION}") file(STRINGS "${ATK_VERSION_HEADER}" ATK_MINOR_VERSION REGEX "^#define ATK_MINOR_VERSION +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define ATK_MINOR_VERSION \\(?([0-9]+)\\)?$" "\\1" ATK_MINOR_VERSION "${ATK_MINOR_VERSION}") file(STRINGS "${ATK_VERSION_HEADER}" ATK_MICRO_VERSION REGEX "^#define ATK_MICRO_VERSION +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define ATK_MICRO_VERSION \\(?([0-9]+)\\)?$" "\\1" ATK_MICRO_VERSION "${ATK_MICRO_VERSION}") set(ATK_VERSION "${ATK_MAJOR_VERSION}.${ATK_MINOR_VERSION}.${ATK_MICRO_VERSION}") unset(ATK_MAJOR_VERSION) unset(ATK_MINOR_VERSION) unset(ATK_MICRO_VERSION) endif() endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(ATK REQUIRED_VARS ATK_LIBRARY VERSION_VAR ATK_VERSION)dino-0.1.0/cmake/FindCairo.cmake0000644000000000000000000000274313614354364015076 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(Cairo PKG_CONFIG_NAME cairo LIB_NAMES cairo INCLUDE_NAMES cairo.h INCLUDE_DIR_SUFFIXES cairo cairo/include ) if(Cairo_FOUND AND NOT Cairo_VERSION) find_file(Cairo_VERSION_HEADER "cairo-version.h" HINTS ${Cairo_INCLUDE_DIRS}) mark_as_advanced(Cairo_VERSION_HEADER) if(Cairo_VERSION_HEADER) file(STRINGS "${Cairo_VERSION_HEADER}" Cairo_MAJOR_VERSION REGEX "^#define CAIRO_VERSION_MAJOR +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define CAIRO_VERSION_MAJOR \\(?([0-9]+)\\)?$" "\\1" Cairo_MAJOR_VERSION "${Cairo_MAJOR_VERSION}") file(STRINGS "${Cairo_VERSION_HEADER}" Cairo_MINOR_VERSION REGEX "^#define CAIRO_VERSION_MINOR +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define CAIRO_VERSION_MINOR \\(?([0-9]+)\\)?$" "\\1" Cairo_MINOR_VERSION "${Cairo_MINOR_VERSION}") file(STRINGS "${Cairo_VERSION_HEADER}" Cairo_MICRO_VERSION REGEX "^#define CAIRO_VERSION_MICRO +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define CAIRO_VERSION_MICRO \\(?([0-9]+)\\)?$" "\\1" Cairo_MICRO_VERSION "${Cairo_MICRO_VERSION}") set(Cairo_VERSION "${Cairo_MAJOR_VERSION}.${Cairo_MINOR_VERSION}.${Cairo_MICRO_VERSION}") unset(Cairo_MAJOR_VERSION) unset(Cairo_MINOR_VERSION) unset(Cairo_MICRO_VERSION) endif() endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Cairo REQUIRED_VARS Cairo_LIBRARY VERSION_VAR Cairo_VERSION)dino-0.1.0/cmake/FindCanberra.cmake0000644000000000000000000000042313614354364015547 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(Canberra PKG_CONFIG_NAME libcanberra LIB_NAMES canberra INCLUDE_NAMES canberra.h ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Canberra REQUIRED_VARS Canberra_LIBRARY) dino-0.1.0/cmake/FindGCrypt.cmake0000644000000000000000000000046613614354364015251 0ustar rootrootinclude(PkgConfigWithFallbackOnConfigScript) find_pkg_config_with_fallback_on_config_script(GCrypt PKG_CONFIG_NAME libgcrypt CONFIG_SCRIPT_NAME libgcrypt ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(GCrypt REQUIRED_VARS GCrypt_LIBRARY VERSION_VAR GCrypt_VERSION) dino-0.1.0/cmake/FindGDK3.cmake0000644000000000000000000000333413614354364014526 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(GDK3 PKG_CONFIG_NAME gdk-3.0 LIB_NAMES gdk-3 INCLUDE_NAMES gdk/gdk.h INCLUDE_DIR_SUFFIXES gtk-3.0 gtk-3.0/include gtk+-3.0 gtk+-3.0/include DEPENDS Pango Cairo GDKPixbuf2 ) if(GDK3_FOUND AND NOT GDK3_VERSION) find_file(GDK3_VERSION_HEADER "gdk/gdkversionmacros.h" HINTS ${GDK3_INCLUDE_DIRS}) mark_as_advanced(GDK3_VERSION_HEADER) if(GDK3_VERSION_HEADER) file(STRINGS "${GDK3_VERSION_HEADER}" GDK3_MAJOR_VERSION REGEX "^#define GDK_MAJOR_VERSION +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define GDK_MAJOR_VERSION \\(?([0-9]+)\\)?$" "\\1" GDK3_MAJOR_VERSION "${GDK3_MAJOR_VERSION}") file(STRINGS "${GDK3_VERSION_HEADER}" GDK3_MINOR_VERSION REGEX "^#define GDK_MINOR_VERSION +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define GDK_MINOR_VERSION \\(?([0-9]+)\\)?$" "\\1" GDK3_MINOR_VERSION "${GDK3_MINOR_VERSION}") file(STRINGS "${GDK3_VERSION_HEADER}" GDK3_MICRO_VERSION REGEX "^#define GDK_MICRO_VERSION +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define GDK_MICRO_VERSION \\(?([0-9]+)\\)?$" "\\1" GDK3_MICRO_VERSION "${GDK3_MICRO_VERSION}") set(GDK3_VERSION "${GDK3_MAJOR_VERSION}.${GDK3_MINOR_VERSION}.${GDK3_MICRO_VERSION}") unset(GDK3_MAJOR_VERSION) unset(GDK3_MINOR_VERSION) unset(GDK3_MICRO_VERSION) endif() endif() if (GDK3_FOUND) find_file(GDK3_WITH_X11 "gdk/gdkx.h" HINTS ${GDK3_INCLUDE_DIRS}) if (GDK3_WITH_X11) set(GDK3_WITH_X11 yes CACHE INTERNAL "Does GDK3 support X11") endif (GDK3_WITH_X11) endif () include(FindPackageHandleStandardArgs) find_package_handle_standard_args(GDK3 REQUIRED_VARS GDK3_LIBRARY VERSION_VAR GDK3_VERSION)dino-0.1.0/cmake/FindGDKPixbuf2.cmake0000644000000000000000000000170413614354364015702 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(GDKPixbuf2 PKG_CONFIG_NAME gdk-pixbuf-2.0 LIB_NAMES gdk_pixbuf-2.0 INCLUDE_NAMES gdk-pixbuf/gdk-pixbuf.h INCLUDE_DIR_SUFFIXES gdk-pixbuf-2.0 gdk-pixbuf-2.0/include DEPENDS GLib ) if(GDKPixbuf2_FOUND AND NOT GDKPixbuf2_VERSION) find_file(GDKPixbuf2_FEATURES_HEADER "gdk-pixbuf/gdk-pixbuf-features.h" HINTS ${GDKPixbuf2_INCLUDE_DIRS}) mark_as_advanced(GDKPixbuf2_FEATURES_HEADER) if(GDKPixbuf2_FEATURES_HEADER) file(STRINGS "${GDKPixbuf2_FEATURES_HEADER}" GDKPixbuf2_VERSION REGEX "^#define GDK_PIXBUF_VERSION \\\"[^\\\"]+\\\"") string(REGEX REPLACE "^#define GDK_PIXBUF_VERSION \\\"([0-9]+)\\.([0-9]+)\\.([0-9]+)\\\"$" "\\1.\\2.\\3" GDKPixbuf2_VERSION "${GDKPixbuf2_VERSION}") endif() endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(GDKPixbuf2 REQUIRED_VARS GDKPixbuf2_LIBRARY VERSION_VAR GDKPixbuf2_VERSION)dino-0.1.0/cmake/FindGIO.cmake0000644000000000000000000000075113614354364014454 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(GIO PKG_CONFIG_NAME gio-2.0 LIB_NAMES gio-2.0 INCLUDE_NAMES gio/gio.h INCLUDE_DIR_SUFFIXES glib-2.0 glib-2.0/include DEPENDS GObject ) if(GIO_FOUND AND NOT GIO_VERSION) # TODO find_package(GLib ${GLib_GLOBAL_VERSION}) set(GIO_VERSION ${GLib_VERSION}) endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(GIO REQUIRED_VARS GIO_LIBRARY VERSION_VAR GIO_VERSION)dino-0.1.0/cmake/FindGLib.cmake0000644000000000000000000000305713614354364014655 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(GLib PKG_CONFIG_NAME glib-2.0 LIB_NAMES glib-2.0 INCLUDE_NAMES glib.h glibconfig.h INCLUDE_DIR_HINTS ${CMAKE_LIBRARY_PATH} ${CMAKE_SYSTEM_LIBRARY_PATH} INCLUDE_DIR_PATHS ${CMAKE_PREFIX_PATH}/lib64 ${CMAKE_PREFIX_PATH}/lib INCLUDE_DIR_SUFFIXES glib-2.0 glib-2.0/include ) if(GLib_FOUND AND NOT GLib_VERSION) find_file(GLib_CONFIG_HEADER "glibconfig.h" HINTS ${GLib_INCLUDE_DIRS}) mark_as_advanced(GLib_CONFIG_HEADER) if(GLib_CONFIG_HEADER) file(STRINGS "${GLib_CONFIG_HEADER}" GLib_MAJOR_VERSION REGEX "^#define GLIB_MAJOR_VERSION +([0-9]+)") string(REGEX REPLACE "^#define GLIB_MAJOR_VERSION ([0-9]+)$" "\\1" GLib_MAJOR_VERSION "${GLib_MAJOR_VERSION}") file(STRINGS "${GLib_CONFIG_HEADER}" GLib_MINOR_VERSION REGEX "^#define GLIB_MINOR_VERSION +([0-9]+)") string(REGEX REPLACE "^#define GLIB_MINOR_VERSION ([0-9]+)$" "\\1" GLib_MINOR_VERSION "${GLib_MINOR_VERSION}") file(STRINGS "${GLib_CONFIG_HEADER}" GLib_MICRO_VERSION REGEX "^#define GLIB_MICRO_VERSION +([0-9]+)") string(REGEX REPLACE "^#define GLIB_MICRO_VERSION ([0-9]+)$" "\\1" GLib_MICRO_VERSION "${GLib_MICRO_VERSION}") set(GLib_VERSION "${GLib_MAJOR_VERSION}.${GLib_MINOR_VERSION}.${GLib_MICRO_VERSION}") unset(GLib_MAJOR_VERSION) unset(GLib_MINOR_VERSION) unset(GLib_MICRO_VERSION) endif() endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(GLib REQUIRED_VARS GLib_LIBRARY VERSION_VAR GLib_VERSION)dino-0.1.0/cmake/FindGModule.cmake0000644000000000000000000000101213614354364015361 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(GModule PKG_CONFIG_NAME gmodule-2.0 LIB_NAMES gmodule-2.0 INCLUDE_NAMES gmodule.h INCLUDE_DIR_SUFFIXES glib-2.0 glib-2.0/include DEPENDS GLib ) if(GModule_FOUND AND NOT GModule_VERSION) # TODO find_package(GLib ${GLib_GLOBAL_VERSION}) set(GModule_VERSION ${GLib_VERSION}) endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(GModule REQUIRED_VARS GModule_LIBRARY VERSION_VAR GModule_VERSION)dino-0.1.0/cmake/FindGObject.cmake0000644000000000000000000000102213614354364015343 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(GObject PKG_CONFIG_NAME gobject-2.0 LIB_NAMES gobject-2.0 INCLUDE_NAMES gobject/gobject.h INCLUDE_DIR_SUFFIXES glib-2.0 glib-2.0/include DEPENDS GLib ) if(GObject_FOUND AND NOT GObject_VERSION) # TODO find_package(GLib ${GLib_GLOBAL_VERSION}) set(GObject_VERSION ${GLib_VERSION}) endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(GObject REQUIRED_VARS GObject_LIBRARY VERSION_VAR GObject_VERSION)dino-0.1.0/cmake/FindGPGME.cmake0000644000000000000000000000045213614354364014673 0ustar rootrootinclude(PkgConfigWithFallbackOnConfigScript) find_pkg_config_with_fallback_on_config_script(GPGME PKG_CONFIG_NAME gpgme CONFIG_SCRIPT_NAME gpgme ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(GPGME REQUIRED_VARS GPGME_LIBRARY VERSION_VAR GPGME_VERSION) dino-0.1.0/cmake/FindGTK3.cmake0000644000000000000000000000276413614354364014554 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(GTK3 PKG_CONFIG_NAME gtk+-3.0 LIB_NAMES gtk-3 INCLUDE_NAMES gtk/gtk.h INCLUDE_DIR_SUFFIXES gtk-3.0 gtk-3.0/include gtk+-3.0 gtk+-3.0/include DEPENDS GDK3 ATK ) if(GTK3_FOUND AND NOT GTK3_VERSION) find_file(GTK3_VERSION_HEADER "gtk/gtkversion.h" HINTS ${GTK3_INCLUDE_DIRS}) mark_as_advanced(GTK3_VERSION_HEADER) if(GTK3_VERSION_HEADER) file(STRINGS "${GTK3_VERSION_HEADER}" GTK3_MAJOR_VERSION REGEX "^#define GTK_MAJOR_VERSION +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define GTK_MAJOR_VERSION \\(?([0-9]+)\\)?$" "\\1" GTK3_MAJOR_VERSION "${GTK3_MAJOR_VERSION}") file(STRINGS "${GTK3_VERSION_HEADER}" GTK3_MINOR_VERSION REGEX "^#define GTK_MINOR_VERSION +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define GTK_MINOR_VERSION \\(?([0-9]+)\\)?$" "\\1" GTK3_MINOR_VERSION "${GTK3_MINOR_VERSION}") file(STRINGS "${GTK3_VERSION_HEADER}" GTK3_MICRO_VERSION REGEX "^#define GTK_MICRO_VERSION +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define GTK_MICRO_VERSION \\(?([0-9]+)\\)?$" "\\1" GTK3_MICRO_VERSION "${GTK3_MICRO_VERSION}") set(GTK3_VERSION "${GTK3_MAJOR_VERSION}.${GTK3_MINOR_VERSION}.${GTK3_MICRO_VERSION}") unset(GTK3_MAJOR_VERSION) unset(GTK3_MINOR_VERSION) unset(GTK3_MICRO_VERSION) endif() endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(GTK3 REQUIRED_VARS GTK3_LIBRARY VERSION_VAR GTK3_VERSION) dino-0.1.0/cmake/FindGee.cmake0000644000000000000000000000053213614354364014533 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(Gee PKG_CONFIG_NAME gee-0.8 LIB_NAMES gee-0.8 INCLUDE_NAMES gee.h INCLUDE_DIR_SUFFIXES gee-0.8 gee-0.8/include DEPENDS GObject ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Gee REQUIRED_VARS Gee_LIBRARY VERSION_VAR Gee_VERSION)dino-0.1.0/cmake/FindGettext.cmake0000644000000000000000000000160513614354364015461 0ustar rootrootfind_program(XGETTEXT_EXECUTABLE xgettext) find_program(MSGMERGE_EXECUTABLE msgmerge) find_program(MSGFMT_EXECUTABLE msgfmt) find_program(MSGCAT_EXECUTABLE msgcat) mark_as_advanced(XGETTEXT_EXECUTABLE MSGMERGE_EXECUTABLE MSGFMT_EXECUTABLE MSGCAT_EXECUTABLE) if(XGETTEXT_EXECUTABLE) execute_process(COMMAND ${XGETTEXT_EXECUTABLE} "--version" OUTPUT_VARIABLE Gettext_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE) string(REGEX REPLACE "xgettext \\(GNU gettext-tools\\) ([0-9\\.]*).*" "\\1" Gettext_VERSION "${Gettext_VERSION}") endif(XGETTEXT_EXECUTABLE) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Gettext FOUND_VAR Gettext_FOUND REQUIRED_VARS XGETTEXT_EXECUTABLE MSGMERGE_EXECUTABLE MSGFMT_EXECUTABLE MSGCAT_EXECUTABLE VERSION_VAR Gettext_VERSION) set(GETTEXT_USE_FILE "${CMAKE_CURRENT_LIST_DIR}/UseGettext.cmake")dino-0.1.0/cmake/FindICU.cmake0000644000000000000000000000045013614354364014452 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(ICU PKG_CONFIG_NAME icu-uc LIB_NAMES icuuc icudata INCLUDE_NAMES unicode/umachine.h ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(ICU REQUIRED_VARS ICU_LIBRARY VERSION_VAR ICU_VERSION) dino-0.1.0/cmake/FindPango.cmake0000644000000000000000000000306113614354364015077 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(Pango PKG_CONFIG_NAME pango LIB_NAMES pango-1.0 INCLUDE_NAMES pango/pango.h INCLUDE_DIR_SUFFIXES pango-1.0 pango-1.0/include DEPENDS GObject ) if(Pango_FOUND AND NOT Pango_VERSION) find_file(Pango_FEATURES_HEADER "pango/pango-features.h" HINTS ${Pango_INCLUDE_DIRS}) mark_as_advanced(Pango_FEATURES_HEADER) if(Pango_FEATURES_HEADER) file(STRINGS "${Pango_FEATURES_HEADER}" Pango_MAJOR_VERSION REGEX "^#define PANGO_VERSION_MAJOR +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define PANGO_VERSION_MAJOR \\(?([0-9]+)\\)?$" "\\1" Pango_MAJOR_VERSION "${Pango_MAJOR_VERSION}") file(STRINGS "${Pango_FEATURES_HEADER}" Pango_MINOR_VERSION REGEX "^#define PANGO_VERSION_MINOR +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define PANGO_VERSION_MINOR \\(?([0-9]+)\\)?$" "\\1" Pango_MINOR_VERSION "${Pango_MINOR_VERSION}") file(STRINGS "${Pango_FEATURES_HEADER}" Pango_MICRO_VERSION REGEX "^#define PANGO_VERSION_MICRO +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define PANGO_VERSION_MICRO \\(?([0-9]+)\\)?$" "\\1" Pango_MICRO_VERSION "${Pango_MICRO_VERSION}") set(Pango_VERSION "${Pango_MAJOR_VERSION}.${Pango_MINOR_VERSION}.${Pango_MICRO_VERSION}") unset(Pango_MAJOR_VERSION) unset(Pango_MINOR_VERSION) unset(Pango_MICRO_VERSION) endif() endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Pango FOUND_VAR Pango_FOUND REQUIRED_VARS Pango_LIBRARY VERSION_VAR Pango_VERSION )dino-0.1.0/cmake/FindQrencode.cmake0000644000000000000000000000046413614354364015577 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(Qrencode PKG_CONFIG_NAME libqrencode LIB_NAMES qrencode INCLUDE_NAMES qrencode.h ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Qrencode REQUIRED_VARS Qrencode_LIBRARY VERSION_VAR Qrencode_VERSION) dino-0.1.0/cmake/FindSQLite3.cmake0000644000000000000000000000135513614354364015263 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(SQLite3 PKG_CONFIG_NAME sqlite3 LIB_NAMES sqlite3 INCLUDE_NAMES sqlite3.h ) if(SQLite3_FOUND AND NOT SQLite3_VERSION) find_file(SQLite3_HEADER "sqlite3.h" HINTS ${SQLite3_INCLUDE_DIRS}) mark_as_advanced(SQLite3_HEADER) if(SQLite3_HEADER) file(STRINGS "${SQLite3_HEADER}" SQLite3_VERSION REGEX "^#define SQLITE_VERSION +\\\"[^\\\"]+\\\"") string(REGEX REPLACE "^#define SQLITE_VERSION +\\\"([0-9]+)\\.([0-9]+)\\.([0-9]+)\\\"$" "\\1.\\2.\\3" SQLite3_VERSION "${SQLite3_VERSION}") endif() endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(SQLite3 REQUIRED_VARS SQLite3_LIBRARY VERSION_VAR SQLite3_VERSION)dino-0.1.0/cmake/FindSignalProtocol.cmake0000644000000000000000000000055413614354364016776 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(SignalProtocol PKG_CONFIG_NAME libsignal-protocol-c LIB_NAMES signal-protocol-c INCLUDE_NAMES signal/signal_protocol.h ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(SignalProtocol REQUIRED_VARS SignalProtocol_LIBRARY VERSION_VAR SignalProtocol_VERSION) dino-0.1.0/cmake/FindSoup.cmake0000644000000000000000000000301413614354364014757 0ustar rootrootinclude(PkgConfigWithFallback) find_pkg_config_with_fallback(Soup PKG_CONFIG_NAME libsoup-2.4 LIB_NAMES soup-2.4 INCLUDE_NAMES libsoup/soup.h INCLUDE_DIR_SUFFIXES libsoup-2.4 libsoup-2.4/include libsoup libsoup/include DEPENDS GIO ) if(Soup_FOUND AND NOT Soup_VERSION) find_file(Soup_VERSION_HEADER "libsoup/soup-version.h" HINTS ${Soup_INCLUDE_DIRS}) mark_as_advanced(Soup_VERSION_HEADER) if(Soup_VERSION_HEADER) file(STRINGS "${Soup_VERSION_HEADER}" Soup_MAJOR_VERSION REGEX "^#define SOUP_MAJOR_VERSION +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define SOUP_MAJOR_VERSION \\(?([0-9]+)\\)?$" "\\1" Soup_MAJOR_VERSION "${Soup_MAJOR_VERSION}") file(STRINGS "${Soup_VERSION_HEADER}" Soup_MINOR_VERSION REGEX "^#define SOUP_MINOR_VERSION +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define SOUP_MINOR_VERSION \\(?([0-9]+)\\)?$" "\\1" Soup_MINOR_VERSION "${Soup_MINOR_VERSION}") file(STRINGS "${Soup_VERSION_HEADER}" Soup_MICRO_VERSION REGEX "^#define SOUP_MICRO_VERSION +\\(?([0-9]+)\\)?$") string(REGEX REPLACE "^#define SOUP_MICRO_VERSION \\(?([0-9]+)\\)?$" "\\1" Soup_MICRO_VERSION "${Soup_MICRO_VERSION}") set(Soup_VERSION "${Soup_MAJOR_VERSION}.${Soup_MINOR_VERSION}.${Soup_MICRO_VERSION}") unset(Soup_MAJOR_VERSION) unset(Soup_MINOR_VERSION) unset(Soup_MICRO_VERSION) endif() endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Soup REQUIRED_VARS Soup_LIBRARY VERSION_VAR Soup_VERSION) dino-0.1.0/cmake/FindVala.cmake0000644000000000000000000000625213614354364014723 0ustar rootroot## # Find module for the Vala compiler (valac) # # This module determines wheter a Vala compiler is installed on the current # system and where its executable is. # # Call the module using "find_package(Vala) from within your CMakeLists.txt. # # The following variables will be set after an invocation: # # VALA_FOUND Whether the vala compiler has been found or not # VALA_EXECUTABLE Full path to the valac executable if it has been found # VALA_VERSION Version number of the available valac # VALA_USE_FILE Include this file to define the vala_precompile function ## ## # Copyright 2009-2010 Jakob Westhoff. All rights reserved. # Copyright 2010-2011 Daniel Pfeifer # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # # 2. 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. # # THIS SOFTWARE IS PROVIDED BY JAKOB WESTHOFF ``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 JAKOB WESTHOFF 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. # # The views and conclusions contained in the software and documentation are those # of the authors and should not be interpreted as representing official policies, # either expressed or implied, of Jakob Westhoff ## # Search for the valac executable in the usual system paths # Some distributions rename the valac to contain the major.minor in the binary name find_package(GObject REQUIRED) find_program(VALA_EXECUTABLE NAMES valac valac-0.38 valac-0.36 valac-0.34 valac-0.32) mark_as_advanced(VALA_EXECUTABLE) # Determine the valac version if(VALA_EXECUTABLE) file(TO_NATIVE_PATH "${VALA_EXECUTABLE}" VALA_EXECUTABLE) execute_process(COMMAND ${VALA_EXECUTABLE} "--version" OUTPUT_VARIABLE VALA_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE) string(REPLACE "Vala " "" VALA_VERSION "${VALA_VERSION}") endif(VALA_EXECUTABLE) # Handle the QUIETLY and REQUIRED arguments, which may be given to the find call. # Furthermore set VALA_FOUND to TRUE if Vala has been found (aka. # VALA_EXECUTABLE is set) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Vala FOUND_VAR VALA_FOUND REQUIRED_VARS VALA_EXECUTABLE VERSION_VAR VALA_VERSION) set(VALA_USE_FILE "${CMAKE_CURRENT_LIST_DIR}/UseVala.cmake") dino-0.1.0/cmake/GenerateGXML.cmake0000644000000000000000000001240113614354364015452 0ustar rootrootinclude(CMakeParseArguments) # Generates the resource XML controlling file from resource list (and saves it # to xml_path). It's not recommended to use this function directly, since it # doesn't handle invalid arguments. It is used by the function # COMPILE_GRESOURCES() to create a custom command, so that this function is # invoked at build-time in script mode from CMake. function(GENERATE_GXML xml_path) # Available options: # COMPRESS_ALL, NO_COMPRESS_ALL Overrides the COMPRESS flag in all # registered resources. # STRIPBLANKS_ALL, NO_STRIPBLANKS_ALL Overrides the STRIPBLANKS flag in all # registered resources. # TOPIXDATA_ALL, NO_TOPIXDATA_ALL Overrides the TOPIXDATA flag in all # registered resources. set(GXML_OPTIONS COMPRESS_ALL NO_COMPRESS_ALL STRIPBLANKS_ALL NO_STRIPBLANKS_ALL TOPIXDATA_ALL NO_TOPIXDATA_ALL) # Available one value options: # PREFIX Overrides the resource prefix that is prepended to each # relative file name in registered resources. set(GXML_ONEVALUEARGS PREFIX) # Available multi-value options: # RESOURCES The list of resource files. Whether absolute or relative path is # equal, absolute paths are stripped down to relative ones. If the # absolute path is not inside the given base directory SOURCE_DIR # or CMAKE_SOURCE_DIR (if SOURCE_DIR is not overriden), this # function aborts. set(GXML_MULTIVALUEARGS RESOURCES) # Parse the arguments. cmake_parse_arguments(GXML_ARG "${GXML_OPTIONS}" "${GXML_ONEVALUEARGS}" "${GXML_MULTIVALUEARGS}" "${ARGN}") # Variable to store the double-quote (") string. Since escaping # double-quotes in strings is not possible we need a helper variable that # does this job for us. set(Q \") # Process resources and generate XML file. # Begin with the XML header and header nodes. set(GXML_XML_FILE "") set(GXML_XML_FILE "${GXML_XML_FILE}") # Process each resource. foreach(res ${GXML_ARG_RESOURCES}) if ("${res}" STREQUAL "COMPRESS") set(GXML_COMPRESSION_FLAG ON) elseif ("${res}" STREQUAL "STRIPBLANKS") set(GXML_STRIPBLANKS_FLAG ON) elseif ("${res}" STREQUAL "TOPIXDATA") set(GXML_TOPIXDATA_FLAG ON) else() # The file name. set(GXML_RESOURCE_PATH "${res}") # Append to real resource file dependency list. list(APPEND GXML_RESOURCES_DEPENDENCIES ${GXML_RESOURCE_PATH}) # Assemble node. set(GXML_RES_LINE "${GXML_RESOURCE_PATH}") # Append to file string. set(GXML_XML_FILE "${GXML_XML_FILE}${GXML_RES_LINE}") # Unset variables. unset(GXML_COMPRESSION_FLAG) unset(GXML_STRIPBLANKS_FLAG) unset(GXML_TOPIXDATA_FLAG) endif() endforeach() # Append closing nodes. set(GXML_XML_FILE "${GXML_XML_FILE}") # Use "file" function to generate XML controlling file. get_filename_component(xml_path_only_name "${xml_path}" NAME) file(WRITE ${xml_path} ${GXML_XML_FILE}) endfunction() dino-0.1.0/cmake/GlibCompileResourcesSupport.cmake0000644000000000000000000000062413614354364020712 0ustar rootroot# Path to this file. set(GCR_CMAKE_MACRO_DIR ${CMAKE_CURRENT_LIST_DIR}) # Finds the glib-compile-resources executable. find_program(GLIB_COMPILE_RESOURCES_EXECUTABLE glib-compile-resources) mark_as_advanced(GLIB_COMPILE_RESOURCES_EXECUTABLE) # Include the cmake files containing the functions. include(${GCR_CMAKE_MACRO_DIR}/CompileGResources.cmake) include(${GCR_CMAKE_MACRO_DIR}/GenerateGXML.cmake) dino-0.1.0/cmake/LargeFileOffsets.c0000644000000000000000000000043713614354364015564 0ustar rootroot#include #define _K ((off_t)1024) #define _M ((off_t)1024 * _K) #define _G ((off_t)1024 * _M) #define _T ((off_t)1024 * _G) int test[(((64 * _G -1) % 671088649) == 268434537) && (((_T - (64 * _G -1) + 255) % 1792151290) == 305159546)? 1: -1]; int main() { return 0; }dino-0.1.0/cmake/MultiFind.cmake0000644000000000000000000000277613614354364015141 0ustar rootrootinclude(CMakeParseArguments) function(find_packages result) cmake_parse_arguments(ARGS "" "" "REQUIRED;OPTIONAL" ${ARGN}) set(_res "") set(_res_libs "") foreach(pkg ${ARGS_REQUIRED}) string(REPLACE ">=" ";" pkg_ ${pkg}) list(GET pkg_ "0" pkg) list(LENGTH pkg_ pkg_has_version) if(pkg_has_version GREATER 1) list(GET pkg_ "1" pkg_version) else() if(${pkg}_GLOBAL_VERSION) set(pkg_version ${${pkg}_GLOBAL_VERSION}) else() unset(pkg_version) endif() endif() find_package(${pkg} ${pkg_version} REQUIRED) list(APPEND _res ${${pkg}_PKG_CONFIG_NAME}) list(APPEND _res_libs ${${pkg}_LIBRARIES}) endforeach(pkg) foreach(pkg ${ARGS_OPTIONAL}) string(REPLACE ">=" ";" pkg_ ${pkg}) list(GET pkg_ "0" pkg) list(LENGTH pkg_ pkg_has_version) if(pkg_has_version GREATER 1) list(GET pkg_ "1" pkg_version) else() if(${pkg}_GLOBAL_VERSION) set(pkg_version ${${pkg}_GLOBAL_VERSION}) else() unset(pkg_version) endif() endif() find_package(${pkg} ${pkg_version}) if(${pkg}_FOUND) list(APPEND _res ${${pkg}_PKG_CONFIG_NAME}) list(APPEND _res_libs ${${pkg}_LIBRARIES}) endif() endforeach(pkg) set(${result} "${_res}" PARENT_SCOPE) set(${result}_LIBS "${_res_libs}" PARENT_SCOPE) endfunction() dino-0.1.0/cmake/PkgConfigWithFallback.cmake0000644000000000000000000001162713614354364017364 0ustar rootrootinclude(CMakeParseArguments) function(find_pkg_config_with_fallback name) cmake_parse_arguments(ARGS "" "PKG_CONFIG_NAME" "LIB_NAMES;LIB_DIR_HINTS;INCLUDE_NAMES;INCLUDE_DIR_PATHS;INCLUDE_DIR_HINTS;INCLUDE_DIR_SUFFIXES;DEPENDS" ${ARGN}) set(${name}_PKG_CONFIG_NAME ${ARGS_PKG_CONFIG_NAME} PARENT_SCOPE) find_package(PkgConfig) if(PKG_CONFIG_FOUND) pkg_search_module(${name}_PKG_CONFIG QUIET ${ARGS_PKG_CONFIG_NAME}) endif(PKG_CONFIG_FOUND) if (${name}_PKG_CONFIG_FOUND) # Found via pkg-config, using it's result values set(${name}_FOUND ${${name}_PKG_CONFIG_FOUND}) # Try to find real file name of libraries foreach(lib ${${name}_PKG_CONFIG_LIBRARIES}) find_library(${name}_${lib}_LIBRARY ${lib} HINTS ${${name}_PKG_CONFIG_LIBRARY_DIRS}) mark_as_advanced(${name}_${lib}_LIBRARY) if(NOT ${name}_${lib}_LIBRARY) unset(${name}_FOUND) endif(NOT ${name}_${lib}_LIBRARY) endforeach(lib) if(${name}_FOUND) set(${name}_LIBRARIES "") foreach(lib ${${name}_PKG_CONFIG_LIBRARIES}) list(APPEND ${name}_LIBRARIES ${${name}_${lib}_LIBRARY}) endforeach(lib) list(REMOVE_DUPLICATES ${name}_LIBRARIES) set(${name}_LIBRARIES ${${name}_LIBRARIES} PARENT_SCOPE) list(GET ${name}_LIBRARIES "0" ${name}_LIBRARY) set(${name}_FOUND ${${name}_FOUND} PARENT_SCOPE) set(${name}_INCLUDE_DIRS ${${name}_PKG_CONFIG_INCLUDE_DIRS} PARENT_SCOPE) set(${name}_LIBRARIES ${${name}_PKG_CONFIG_LIBRARIES} PARENT_SCOPE) set(${name}_LIBRARY ${${name}_LIBRARY} PARENT_SCOPE) set(${name}_VERSION ${${name}_PKG_CONFIG_VERSION} PARENT_SCOPE) if(NOT TARGET ${ARGS_PKG_CONFIG_NAME}) add_library(${ARGS_PKG_CONFIG_NAME} INTERFACE IMPORTED) set_property(TARGET ${ARGS_PKG_CONFIG_NAME} PROPERTY INTERFACE_COMPILE_OPTIONS "${${name}_PKG_CONFIG_CFLAGS_OTHER}") set_property(TARGET ${ARGS_PKG_CONFIG_NAME} PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${${name}_PKG_CONFIG_INCLUDE_DIRS}") set_property(TARGET ${ARGS_PKG_CONFIG_NAME} PROPERTY INTERFACE_LINK_LIBRARIES "${${name}_LIBRARIES}") endif(NOT TARGET ${ARGS_PKG_CONFIG_NAME}) endif(${name}_FOUND) else(${name}_PKG_CONFIG_FOUND) # No success with pkg-config, try via find_library on all lib_names set(${name}_FOUND "1") foreach(lib ${ARGS_LIB_NAMES}) find_library(${name}_${lib}_LIBRARY ${ARGS_LIB_NAMES} HINTS ${ARGS_LIB_DIR_HINTS}) mark_as_advanced(${name}_${lib}_LIBRARY) if(NOT ${name}_${lib}_LIBRARY) unset(${name}_FOUND) endif(NOT ${name}_${lib}_LIBRARY) endforeach(lib) foreach(inc ${ARGS_INCLUDE_NAMES}) find_path(${name}_${inc}_INCLUDE_PATH ${inc} HINTS ${ARGS_INCLUDE_DIR_HINTS} PATHS ${ARGS_INCLUDE_DIR_PATHS} PATH_SUFFIXES ${ARGS_INCLUDE_DIR_SUFFIXES}) mark_as_advanced(${name}_${inc}_INCLUDE_PATH) if(NOT ${name}_${inc}_INCLUDE_PATH) unset(${name}_FOUND) endif(NOT ${name}_${inc}_INCLUDE_PATH) endforeach(inc) if(${name}_FOUND) set(${name}_LIBRARIES "") set(${name}_INCLUDE_DIRS "") foreach(lib ${ARGS_LIB_NAMES}) list(APPEND ${name}_LIBRARIES ${${name}_${lib}_LIBRARY}) endforeach(lib) foreach(inc ${ARGS_INCLUDE_NAMES}) list(APPEND ${name}_INCLUDE_DIRS ${${name}_${inc}_INCLUDE_PATH}) endforeach(inc) list(GET ${name}_LIBRARIES "0" ${name}_LIBRARY) foreach(dep ${ARGS_DEPENDS}) find_package(${dep} ${${dep}_GLOBAL_VERSION} QUIET) if(${dep}_FOUND) list(APPEND ${name}_INCLUDE_DIRS ${${dep}_INCLUDE_DIRS}) list(APPEND ${name}_LIBRARIES ${${dep}_LIBRARIES}) else(${dep}_FOUND) unset(${name}_FOUND) endif(${dep}_FOUND) endforeach(dep) set(${name}_FOUND ${${name}_FOUND} PARENT_SCOPE) set(${name}_INCLUDE_DIRS ${${name}_INCLUDE_DIRS} PARENT_SCOPE) set(${name}_LIBRARIES ${${name}_LIBRARIES} PARENT_SCOPE) set(${name}_LIBRARY ${${name}_LIBRARY} PARENT_SCOPE) unset(${name}_VERSION PARENT_SCOPE) if(NOT TARGET ${ARGS_PKG_CONFIG_NAME}) add_library(${ARGS_PKG_CONFIG_NAME} INTERFACE IMPORTED) set_property(TARGET ${ARGS_PKG_CONFIG_NAME} PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${${name}_INCLUDE_DIRS}") set_property(TARGET ${ARGS_PKG_CONFIG_NAME} PROPERTY INTERFACE_LINK_LIBRARIES "${${name}_LIBRARIES}") endif(NOT TARGET ${ARGS_PKG_CONFIG_NAME}) endif(${name}_FOUND) endif(${name}_PKG_CONFIG_FOUND) endfunction() dino-0.1.0/cmake/PkgConfigWithFallbackOnConfigScript.cmake0000644000000000000000000001262013614354364022166 0ustar rootrootinclude(CMakeParseArguments) function(find_pkg_config_with_fallback_on_config_script name) cmake_parse_arguments(ARGS "" "PKG_CONFIG_NAME" "CONFIG_SCRIPT_NAME" ${ARGN}) set(${name}_PKG_CONFIG_NAME ${ARGS_PKG_CONFIG_NAME} PARENT_SCOPE) find_package(PkgConfig) if(PKG_CONFIG_FOUND) pkg_search_module(${name}_PKG_CONFIG QUIET ${ARGS_PKG_CONFIG_NAME}) endif(PKG_CONFIG_FOUND) if (${name}_PKG_CONFIG_FOUND) # Found via pkg-config, using it's result values set(${name}_FOUND ${${name}_PKG_CONFIG_FOUND}) # Try to find real file name of libraries foreach(lib ${${name}_PKG_CONFIG_LIBRARIES}) find_library(${name}_${lib}_LIBRARY ${lib} HINTS ${${name}_PKG_CONFIG_LIBRARY_DIRS}) mark_as_advanced(${name}_${lib}_LIBRARY) if(NOT ${name}_${lib}_LIBRARY) unset(${name}_FOUND) endif(NOT ${name}_${lib}_LIBRARY) endforeach(lib) if(${name}_FOUND) set(${name}_LIBRARIES "") foreach(lib ${${name}_PKG_CONFIG_LIBRARIES}) list(APPEND ${name}_LIBRARIES ${${name}_${lib}_LIBRARY}) endforeach(lib) list(REMOVE_DUPLICATES ${name}_LIBRARIES) set(${name}_LIBRARIES ${${name}_LIBRARIES} PARENT_SCOPE) list(GET ${name}_LIBRARIES "0" ${name}_LIBRARY) set(${name}_FOUND ${${name}_FOUND} PARENT_SCOPE) set(${name}_INCLUDE_DIRS ${${name}_PKG_CONFIG_INCLUDE_DIRS} PARENT_SCOPE) set(${name}_LIBRARIES ${${name}_PKG_CONFIG_LIBRARIES} PARENT_SCOPE) set(${name}_LIBRARY ${${name}_LIBRARY} PARENT_SCOPE) set(${name}_VERSION ${${name}_PKG_CONFIG_VERSION} PARENT_SCOPE) if(NOT TARGET ${ARGS_PKG_CONFIG_NAME}) add_library(${ARGS_PKG_CONFIG_NAME} INTERFACE IMPORTED) set_property(TARGET ${ARGS_PKG_CONFIG_NAME} PROPERTY INTERFACE_COMPILE_OPTIONS "${${name}_PKG_CONFIG_CFLAGS_OTHER}") set_property(TARGET ${ARGS_PKG_CONFIG_NAME} PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${${name}_PKG_CONFIG_INCLUDE_DIRS}") set_property(TARGET ${ARGS_PKG_CONFIG_NAME} PROPERTY INTERFACE_LINK_LIBRARIES "${${name}_LIBRARIES}") endif(NOT TARGET ${ARGS_PKG_CONFIG_NAME}) endif(${name}_FOUND) else(${name}_PKG_CONFIG_FOUND) # No success with pkg-config, try via custom a *-config script find_program(${name}_CONFIG_EXECUTABLE NAMES ${ARGS_CONFIG_SCRIPT_NAME}-config) mark_as_advanced(${name}_CONFIG_EXECUTABLE) find_program(${name}_SH_EXECUTABLE NAMES sh) mark_as_advanced(${name}_SH_EXECUTABLE) if(${name}_CONFIG_EXECUTABLE) macro(config_script_fail errcode) if(${errcode}) message(FATAL_ERROR "Error invoking ${ARGS_CONFIG_SCRIPT_NAME}-config: ${errcode}") endif(${errcode}) endmacro(config_script_fail) file(TO_NATIVE_PATH "${${name}_CONFIG_EXECUTABLE}" ${name}_CONFIG_EXECUTABLE) file(TO_NATIVE_PATH "${${name}_SH_EXECUTABLE}" ${name}_SH_EXECUTABLE) execute_process(COMMAND "${${name}_SH_EXECUTABLE}" "${${name}_CONFIG_EXECUTABLE}" --version OUTPUT_VARIABLE ${name}_VERSION RESULT_VARIABLE ERRCODE OUTPUT_STRIP_TRAILING_WHITESPACE) config_script_fail(${ERRCODE}) execute_process(COMMAND "${${name}_SH_EXECUTABLE}" "${${name}_CONFIG_EXECUTABLE}" --api-version OUTPUT_VARIABLE ${name}_API_VERSION RESULT_VARIABLE ERRCODE OUTPUT_STRIP_TRAILING_WHITESPACE) config_script_fail(${ERRCODE}) execute_process(COMMAND "${${name}_SH_EXECUTABLE}" "${${name}_CONFIG_EXECUTABLE}" --cflags OUTPUT_VARIABLE ${name}_CFLAGS RESULT_VARIABLE ERRCODE OUTPUT_STRIP_TRAILING_WHITESPACE) config_script_fail(${ERRCODE}) execute_process(COMMAND "${${name}_SH_EXECUTABLE}" "${${name}_CONFIG_EXECUTABLE}" --libs OUTPUT_VARIABLE ${name}_LDFLAGS RESULT_VARIABLE ERRCODE OUTPUT_STRIP_TRAILING_WHITESPACE) config_script_fail(${ERRCODE}) string(TOLOWER ${name} "${name}_LOWER") string(REGEX REPLACE "^(.* |)-l([^ ]*${${name}_LOWER}[^ ]*)( .*|)$" "\\2" ${name}_LIBRARY_NAME "${${name}_LDFLAGS}") string(REGEX REPLACE "^(.* |)-L([^ ]*)( .*|)$" "\\2" ${name}_LIBRARY_DIRS "${${name}_LDFLAGS}") find_library(${name}_LIBRARY ${${name}_LIBRARY_NAME} HINTS ${${name}_LIBRARY_DIRS}) mark_as_advanced(${name}_LIBRARY) set(${name}_LIBRARY ${${name}_LIBRARY} PARENT_SCOPE) set(${name}_VERSION ${${name}_VERSION} PARENT_SCOPE) unset(${name}_LIBRARY_NAME) unset(${name}_LIBRARY_DIRS) if(NOT TARGET ${name}_LOWER) add_library(${name}_LOWER INTERFACE IMPORTED) set_property(TARGET ${name}_LOWER PROPERTY INTERFACE_LINK_LIBRARIES "${${name}_LDFLAGS}") set_property(TARGET ${name}_LOWER PROPERTY INTERFACE_COMPILE_OPTIONS "${${name}_CFLAGS}") endif(NOT TARGET ${name}_LOWER) endif(${name}_CONFIG_EXECUTABLE) endif(${name}_PKG_CONFIG_FOUND) endfunction() dino-0.1.0/cmake/UseGettext.cmake0000644000000000000000000000237413614354364015341 0ustar rootrootfunction(_gettext_mkdir_for_file file) get_filename_component(dir "${file}" DIRECTORY) file(MAKE_DIRECTORY "${dir}") endfunction() function(gettext_compile project_name) cmake_parse_arguments(ARGS "" "MO_FILES_NAME;TARGET_NAME;SOURCE_DIR;PROJECT_NAME" "" ${ARGN}) if(NOT ARGS_SOURCE_DIR) set(ARGS_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) endif(NOT ARGS_SOURCE_DIR) file(STRINGS "${ARGS_SOURCE_DIR}/LINGUAS" LINGUAS) set(target_files) foreach(lang ${LINGUAS}) set(source_file ${ARGS_SOURCE_DIR}/${lang}.po) set(target_file ${CMAKE_BINARY_DIR}/locale/${lang}/LC_MESSAGES/${project_name}.mo) _gettext_mkdir_for_file(${target_file}) list(APPEND target_files ${target_file}) add_custom_command(OUTPUT ${target_file} COMMAND ${MSGFMT_EXECUTABLE} --check-format -o ${target_file} ${source_file} DEPENDS ${source_file}) install(FILES ${target_file} DESTINATION ${LOCALE_INSTALL_DIR}/${lang}/LC_MESSAGES) endforeach(lang) if(ARGS_MO_FILES_NAME) set(${ARGS_MO_FILES_NAME} ${target_files} PARENT_SCOPE) endif(ARGS_MO_FILES_NAME) if(ARGS_TARGET_NAME) add_custom_target(${ARGS_TARGET_NAME} DEPENDS ${target_files}) endif(ARGS_TARGET_NAME) endfunction(gettext_compile) dino-0.1.0/cmake/UseVala.cmake0000644000000000000000000003147713614354364014606 0ustar rootroot## # Compile vala files to their c equivalents for further processing. # # The "vala_precompile" function takes care of calling the valac executable on # the given source to produce c files which can then be processed further using # default cmake functions. # # The first parameter provided is a variable, which will be filled with a list # of c files outputted by the vala compiler. This list can than be used in # conjuction with functions like "add_executable" or others to create the # neccessary compile rules with CMake. # # The following sections may be specified afterwards to provide certain options # to the vala compiler: # # SOURCES # A list of .vala files to be compiled. Please take care to add every vala # file belonging to the currently compiled project or library as Vala will # otherwise not be able to resolve all dependencies. # # PACKAGES # A list of vala packages/libraries to be used during the compile cycle. The # package names are exactly the same, as they would be passed to the valac # "--pkg=" option. # # OPTIONS # A list of optional options to be passed to the valac executable. This can be # used to pass "--thread" for example to enable multi-threading support. # # DEFINITIONS # A list of symbols to be used for conditional compilation. They are the same # as they would be passed using the valac "--define=" option. # # CUSTOM_VAPIS # A list of custom vapi files to be included for compilation. This can be # useful to include freshly created vala libraries without having to install # them in the system. # # GENERATE_VAPI # Pass all the needed flags to the compiler to create a vapi for # the compiled library. The provided name will be used for this and a # .vapi file will be created. # # GENERATE_HEADER # Let the compiler generate a header file for the compiled code. There will # be a header file as well as an internal header file being generated called # .h and _internal.h # # The following call is a simple example to the vala_precompile macro showing # an example to every of the optional sections: # # find_package(Vala "0.12" REQUIRED) # include(${VALA_USE_FILE}) # # vala_precompile(VALA_C # SOURCES # source1.vala # source2.vala # source3.vala # PACKAGES # gtk+-2.0 # gio-1.0 # posix # DIRECTORY # gen # OPTIONS # --thread # CUSTOM_VAPIS # some_vapi.vapi # GENERATE_VAPI # myvapi # GENERATE_HEADER # myheader # ) # # Most important is the variable VALA_C which will contain all the generated c # file names after the call. ## ## # Copyright 2009-2010 Jakob Westhoff. All rights reserved. # Copyright 2010-2011 Daniel Pfeifer # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # # 2. 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. # # THIS SOFTWARE IS PROVIDED BY JAKOB WESTHOFF ``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 JAKOB WESTHOFF 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. # # The views and conclusions contained in the software and documentation are those # of the authors and should not be interpreted as representing official policies, # either expressed or implied, of Jakob Westhoff ## include(CMakeParseArguments) function(_vala_mkdir_for_file file) get_filename_component(dir "${file}" DIRECTORY) file(MAKE_DIRECTORY "${dir}") endfunction() function(vala_precompile output) cmake_parse_arguments(ARGS "FAST_VAPI" "DIRECTORY;GENERATE_HEADER;GENERATE_VAPI;EXPORTS_DIR" "SOURCES;PACKAGES;OPTIONS;DEFINITIONS;CUSTOM_VAPIS;CUSTOM_DEPS;GRESOURCES" ${ARGN}) # Header and internal header is needed to generate internal vapi if (ARGS_GENERATE_VAPI AND NOT ARGS_GENERATE_HEADER) set(ARGS_GENERATE_HEADER ${ARGS_GENERATE_VAPI}) endif(ARGS_GENERATE_VAPI AND NOT ARGS_GENERATE_HEADER) if("Ninja" STREQUAL ${CMAKE_GENERATOR} AND NOT DISABLE_FAST_VAPI AND NOT ARGS_GENERATE_HEADER) set(ARGS_FAST_VAPI true) endif() if(ARGS_DIRECTORY) get_filename_component(DIRECTORY ${ARGS_DIRECTORY} ABSOLUTE) else(ARGS_DIRECTORY) set(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) endif(ARGS_DIRECTORY) if(ARGS_EXPORTS_DIR) set(ARGS_EXPORTS_DIR ${CMAKE_BINARY_DIR}/${ARGS_EXPORTS_DIR}) else(ARGS_EXPORTS_DIR) set(ARGS_EXPORTS_DIR ${CMAKE_BINARY_DIR}/exports) endif(ARGS_EXPORTS_DIR) file(MAKE_DIRECTORY "${ARGS_EXPORTS_DIR}") include_directories(${DIRECTORY} ${ARGS_EXPORTS_DIR}) set(vala_pkg_opts "") foreach(pkg ${ARGS_PACKAGES}) list(APPEND vala_pkg_opts "--pkg=${pkg}") endforeach(pkg ${ARGS_PACKAGES}) set(vala_define_opts "") foreach(def ${ARGS_DEFINITIONS}) list(APPEND vala_define_opts "--define=${def}") endforeach(def ${ARGS_DEFINITIONS}) set(custom_vapi_arguments "") if(ARGS_CUSTOM_VAPIS) foreach(vapi ${ARGS_CUSTOM_VAPIS}) if(${vapi} MATCHES ${CMAKE_SOURCE_DIR} OR ${vapi} MATCHES ${CMAKE_BINARY_DIR}) list(APPEND custom_vapi_arguments ${vapi}) else (${vapi} MATCHES ${CMAKE_SOURCE_DIR} OR ${vapi} MATCHES ${CMAKE_BINARY_DIR}) list(APPEND custom_vapi_arguments ${CMAKE_CURRENT_SOURCE_DIR}/${vapi}) endif(${vapi} MATCHES ${CMAKE_SOURCE_DIR} OR ${vapi} MATCHES ${CMAKE_BINARY_DIR}) endforeach(vapi ${ARGS_CUSTOM_VAPIS}) endif(ARGS_CUSTOM_VAPIS) set(gresources_args "") if(ARGS_GRESOURCES) set(gresources_args --gresources "${ARGS_GRESOURCES}") endif(ARGS_GRESOURCES) set(in_files "") set(fast_vapi_files "") set(out_files "") set(out_extra_files "") set(out_deps_files "") set(vapi_arguments "") if(ARGS_GENERATE_VAPI) list(APPEND out_extra_files "${ARGS_EXPORTS_DIR}/${ARGS_GENERATE_VAPI}.vapi") list(APPEND out_extra_files "${ARGS_EXPORTS_DIR}/${ARGS_GENERATE_VAPI}_internal.vapi") set(vapi_arguments "--vapi=${ARGS_EXPORTS_DIR}/${ARGS_GENERATE_VAPI}.vapi" "--internal-vapi=${ARGS_EXPORTS_DIR}/${ARGS_GENERATE_VAPI}_internal.vapi") if(ARGS_PACKAGES) string(REPLACE ";" "\\n" pkgs "${ARGS_PACKAGES};${ARGS_CUSTOM_DEPS}") add_custom_command(OUTPUT "${ARGS_EXPORTS_DIR}/${ARGS_GENERATE_VAPI}.deps" COMMAND echo -e "\"${pkgs}\"" > "${ARGS_EXPORTS_DIR}/${ARGS_GENERATE_VAPI}.deps" COMMENT "Generating ${ARGS_GENERATE_VAPI}.deps") endif(ARGS_PACKAGES) endif(ARGS_GENERATE_VAPI) set(header_arguments "") if(ARGS_GENERATE_HEADER) list(APPEND out_extra_files "${ARGS_EXPORTS_DIR}/${ARGS_GENERATE_HEADER}.h") list(APPEND out_extra_files "${ARGS_EXPORTS_DIR}/${ARGS_GENERATE_HEADER}_internal.h") list(APPEND header_arguments "--header=${ARGS_EXPORTS_DIR}/${ARGS_GENERATE_HEADER}.h") list(APPEND header_arguments "--internal-header=${ARGS_EXPORTS_DIR}/${ARGS_GENERATE_HEADER}_internal.h") endif(ARGS_GENERATE_HEADER) string(REPLACE " " ";" VALAC_FLAGS ${CMAKE_VALA_FLAGS}) if (VALA_VERSION VERSION_GREATER "0.38") set(VALAC_COLORS "--color=always") endif () if(ARGS_FAST_VAPI) foreach(src ${ARGS_SOURCES} ${ARGS_UNPARSED_ARGUMENTS}) set(in_file "${CMAKE_CURRENT_SOURCE_DIR}/${src}") list(APPEND in_files "${in_file}") string(REPLACE ".vala" ".c" src ${src}) string(REPLACE ".gs" ".c" src ${src}) string(REPLACE ".c" ".vapi" fast_vapi ${src}) set(fast_vapi_file "${DIRECTORY}/${fast_vapi}") list(APPEND fast_vapi_files "${fast_vapi_file}") list(APPEND out_files "${DIRECTORY}/${src}") _vala_mkdir_for_file("${fast_vapi_file}") add_custom_command(OUTPUT ${fast_vapi_file} COMMAND ${VALA_EXECUTABLE} ARGS ${VALAC_COLORS} --fast-vapi ${fast_vapi_file} ${vala_define_opts} ${ARGS_OPTIONS} ${VALAC_FLAGS} ${in_file} DEPENDS ${in_file} COMMENT "Generating fast VAPI ${fast_vapi}" ) endforeach(src ${ARGS_SOURCES} ${ARGS_UNPARSED_ARGUMENTS}) foreach(src ${ARGS_SOURCES} ${ARGS_UNPARSED_ARGUMENTS}) set(in_file "${CMAKE_CURRENT_SOURCE_DIR}/${src}") string(REPLACE ".vala" ".c" c_code ${src}) string(REPLACE ".gs" ".c" c_code ${c_code}) string(REPLACE ".c" ".vapi" fast_vapi ${c_code}) set(my_fast_vapi_file "${DIRECTORY}/${fast_vapi}") set(c_code_file "${DIRECTORY}/${c_code}") set(fast_vapi_flags "") set(fast_vapi_stamp "") foreach(fast_vapi_file ${fast_vapi_files}) if(NOT "${fast_vapi_file}" STREQUAL "${my_fast_vapi_file}") list(APPEND fast_vapi_flags --use-fast-vapi "${fast_vapi_file}") list(APPEND fast_vapi_stamp "${fast_vapi_file}") endif() endforeach(fast_vapi_file) _vala_mkdir_for_file("${fast_vapi_file}") get_filename_component(dir "${c_code_file}" DIRECTORY) add_custom_command(OUTPUT ${c_code_file} COMMAND ${VALA_EXECUTABLE} ARGS ${VALAC_COLORS} "-C" "-d" ${dir} ${vala_pkg_opts} ${vala_define_opts} ${gresources_args} ${ARGS_OPTIONS} ${VALAC_FLAGS} ${fast_vapi_flags} ${in_file} ${custom_vapi_arguments} DEPENDS ${fast_vapi_stamp} ${in_file} ${ARGS_CUSTOM_VAPIS} ${ARGS_GRESOURCES} COMMENT "Generating C source ${c_code}" ) endforeach(src) if(NOT "${out_extra_files}" STREQUAL "") add_custom_command(OUTPUT ${out_extra_files} COMMAND ${VALA_EXECUTABLE} ARGS ${VALAC_COLORS} -C -q --disable-warnings ${header_arguments} ${vapi_arguments} "-b" ${CMAKE_CURRENT_SOURCE_DIR} "-d" ${DIRECTORY} ${vala_pkg_opts} ${vala_define_opts} ${gresources_args} ${ARGS_OPTIONS} ${VALAC_FLAGS} ${in_files} ${custom_vapi_arguments} DEPENDS ${in_files} ${ARGS_CUSTOM_VAPIS} ${ARGS_GRESOURCES} COMMENT "Generating VAPI and headers for target ${output}" ) endif() else(ARGS_FAST_VAPI) foreach(src ${ARGS_SOURCES} ${ARGS_UNPARSED_ARGUMENTS}) set(in_file "${CMAKE_CURRENT_SOURCE_DIR}/${src}") list(APPEND in_files "${in_file}") string(REPLACE ".vala" ".c" src ${src}) string(REPLACE ".gs" ".c" src ${src}) list(APPEND out_files "${DIRECTORY}/${src}") _vala_mkdir_for_file("${fast_vapi_file}") endforeach(src ${ARGS_SOURCES} ${ARGS_UNPARSED_ARGUMENTS}) add_custom_command(OUTPUT ${out_files} ${out_extra_files} COMMAND ${VALA_EXECUTABLE} ARGS ${VALAC_COLORS} -C ${header_arguments} ${vapi_arguments} "-b" ${CMAKE_CURRENT_SOURCE_DIR} "-d" ${DIRECTORY} ${vala_pkg_opts} ${vala_define_opts} ${gresources_args} ${ARGS_OPTIONS} ${VALAC_FLAGS} ${in_files} ${custom_vapi_arguments} DEPENDS ${in_files} ${ARGS_CUSTOM_VAPIS} ${ARGS_GRESOURCES} COMMENT "Generating C code for target ${output}" ) endif(ARGS_FAST_VAPI) set(${output} ${out_files} PARENT_SCOPE) endfunction(vala_precompile) dino-0.1.0/cmake/cmake_uninstall.cmake.in0000644000000000000000000000201413614354364017005 0ustar rootrootif(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") message(FATAL_ERROR "Cannot find install manifest: @CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") endif(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) string(REGEX REPLACE "\n" ";" files "${files}") foreach(file ${files}) message(STATUS "Uninstalling: $ENV{DESTDIR}${file}") if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") exec_program( "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" OUTPUT_VARIABLE rm_out RETURN_VALUE rm_retval ) if(NOT "${rm_retval}" STREQUAL 0) message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") endif(NOT "${rm_retval}" STREQUAL 0) else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") message(STATUS "File $ENV{DESTDIR}${file} does not exist.") endif(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") endforeach(file) dino-0.1.0/configure0000755000000000000000000002462213614354364013065 0ustar rootroot#!/bin/sh OPTS=`getopt -o "h" --long \ help,fetch-only,no-debug,disable-fast-vapi,with-tests,release,with-libsignal-in-tree,\ enable-plugin:,disable-plugin:,\ prefix:,program-prefix:,exec-prefix:,lib-suffix:,\ bindir:,libdir:,includedir:,datadir:,\ host:,build:,\ sbindir:,sysconfdir:,libexecdir:,localstatedir:,sharedstatedir:,mandir:,infodir:,\ enable-dependency-tracking,disable-dependency-tracking,\ disable-silent-rules,disable-maintainer-mode\ -n './configure' -- "$@"` if [ $? != 0 ] ; then echo "Failed parsing options." >&2 ; exit 1 ; fi eval set -- "$OPTS" PREFIX=${PREFIX:-/usr/local} ENABLED_PLUGINS= DISABLED_PLUGINS= BUILD_LIBSIGNAL_IN_TREE= BUILD_TESTS= BUILD_TYPE=Debug DISABLE_FAST_VAPI= LIB_SUFFIX= NO_DEBUG= FETCH_ONLY= EXEC_PREFIX= BINDIR= SBINDIR=n SYSCONFDIR= DATADIR= INCLUDEDIR= LIBDIR= LIBEXECDIR= LOCALSTATEDIR= SHAREDSTATEDIR= MANDIR= INFODIR= help() { cat << EOF Usage: ./configure [OPTION]... Defaults for the options (based on current environment) are specified in brackets. Configuration: -h, --help Print this help and exit --disable-fast-vapi Disable the usage of Vala compilers fast-vapi feature. fast-vapi mode is slower when doing clean builds, but faster when doing incremental builds (during development). --fetch-only Only fetch the files required to run ./configure without network access later and exit. --no-debug Build without debug symbols --release Configure to build an optimized release version --with-libsignal-in-tree Build libsignal-protocol-c in tree and link it statically. --with-tests Also build tests. Plugin configuration: --enable-plugin=PLUGIN Enable compilation of plugin PLUGIN. --disable-plugin=PLUGIN Disable compilation of plugin PLUGIN. Installation directories: --prefix=PREFIX Install architecture-independent files in PREFIX [$PREFIX] --program-prefix=PREFIX Same as --prefix --exec-prefix= Install architecture-dependent files in EPREFIX [PREFIX] --lib-suffix=SUFFIX Append SUFFIX to the directory name for libraries By default, \`make install' will install all the files in \`/usr/local/bin', \`/usr/local/lib' etc. You can specify an installation prefix other than \`/usr/local' using \`--prefix', for instance \`--prefix=\$HOME'. For better control, use the options below. Fine tuning of the installation directories: --bindir=DIR user executables [EPREFIX/bin] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --datadir=DIR read-only data [PREFIX/share] For compatibility with autotools, these options will be silently ignored: --host, --build, --sbindir, --sysconfdir, --libexecdir, --sharedstatedir, --localstatedir, --mandir, --infodir, --enable-dependency-tracking, --disable-dependency-tracking Some influential environment variables: CC C compiler command CFLAGS C compiler flags PKG_CONFIG_PATH directories to add to pkg-config's search path PKG_CONFIG_LIBDIR path overriding pkg-config's built-in search path USE_CCACHE decide to use ccache when compiling C objects VALAC Vala compiler command VALACFLAGS Vala compiler flags Use these variables to override the choices made by \`configure' or to help it to find libraries and programs with nonstandard names/locations. EOF } while true; do case "$1" in --prefix ) PREFIX="$2"; shift; shift ;; --enable-plugin ) if [ -z "$ENABLED_PLUGINS" ]; then ENABLED_PLUGINS="$2"; else ENABLED_PLUGINS="$ENABLED_PLUGINS;$2"; fi; shift; shift ;; --disable-plugin ) if [ -z "$DISABLED_PLUGINS" ]; then DISABLED_PLUGINS="$2"; else DISABLED_PLUGINS="$DISABLED_PLUGINS;$2"; fi; shift; shift ;; --valac ) VALA_EXECUTABLE="$2"; shift; shift ;; --valac-flags ) VALAC_FLAGS="$2"; shift; shift ;; --lib-suffix ) LIB_SUFFIX="$2"; shift; shift ;; --with-libsignal-in-tree ) BUILD_LIBSIGNAL_IN_TREE=yes; shift ;; --disable-fast-vapi ) DISABLE_FAST_VAPI=yes; shift ;; --no-debug ) NO_DEBUG=yes; shift ;; --fetch-only ) FETCH_ONLY=yes; shift ;; --release ) BUILD_TYPE=RelWithDebInfo; shift ;; --with-tests ) BUILD_TESTS=yes; shift ;; # Autotools paths --program-prefix ) PREFIX="$2"; shift; shift ;; --exec-prefix ) EXEC_PREFIX="$2"; shift; shift ;; --bindir ) BINDIR="$2"; shift; shift ;; --datadir ) DATADIR="$2"; shift; shift ;; --includedir ) INCLUDEDIR="$2"; shift; shift ;; --libdir ) LIBDIR="$2"; shift; shift ;; # Autotools paths not used --sbindir ) SBINDIR="$2"; shift; shift ;; --sysconfdir ) SYSCONFDIR="$2"; shift; shift ;; --libexecdir ) LIBEXECDIR="$2"; shift; shift ;; --localstatedir ) LOCALSTATEDIR="$2"; shift; shift ;; --sharedstatedir ) SHAREDSTATEDIR="$2"; shift; shift ;; --mandir ) MANDIR="$2"; shift; shift ;; --infodir ) INFODIR="$2"; shift; shift ;; # Ignore for autotools compat --host | --build ) shift; shift ;; --disable-dependency-tracking | --enable-dependency-tracking ) shift ;; # Ignore for debian compat --disable-silent-rules | --disable-maintainer-mode ) shift ;; -h | --help ) help; exit 0 ;; -- ) shift; break ;; * ) break ;; esac done if [ "$BUILD_LIBSIGNAL_IN_TREE" = "yes" ] || [ "$FETCH_ONLY" = "yes" ]; then if [ -d ".git" ]; then git submodule update --init 2>/dev/null else tmp=0 for i in $(cat .gitmodules | grep -n submodule | awk -F ':' '{print $1}') $(wc -l .gitmodules | awk '{print $1}'); do if ! [ $tmp -eq 0 ]; then name=$(cat .gitmodules | head -n $tmp | tail -n 1 | awk -F '"' '{print $2}') def=$(cat .gitmodules | head -n $i | tail -n $(expr "$i" - "$tmp") | awk -F ' ' '{print $1 $2 $3}') path=$(echo "$def" | grep '^path=' | awk -F '=' '{print $2}') url=$(echo "$def" | grep '^url=' | awk -F '=' '{print $2}') branch=$(echo "$def" | grep '^branch=' | awk -F '=' '{print $2}') if ! ls "$path"/* >/dev/null 2>/dev/null; then git=$(which git) if ! [ $? -eq 0 ] || ! [ -x $git ]; then echo "Failed retrieving missing files" exit 5 fi res=$(git clone "$url" "$path" 2>&1) if ! [ $? -eq 0 ] || ! [ -d $path ]; then echo "Failed retrieving missing files: $res" exit 5 fi if [ -n "$branch" ]; then olddir="$(pwd)" cd "$path" res=$(git checkout "$branch" 2>&1) if ! [ $? -eq 0 ]; then echo "Failed retrieving missing files: $res" exit 5 fi cd "$olddir" fi echo "Submodule path '$path': checked out '$branch' (via git clone)" fi fi tmp=$i done fi fi if [ "$FETCH_ONLY" = "yes" ]; then exit 0; fi if [ ! -x "$(which cmake 2>/dev/null)" ] then echo "-!- CMake required." exit 1 fi ninja_bin="$(which ninja-build 2>/dev/null)" if ! [ -x "$ninja_bin" ]; then ninja_bin="$(which ninja 2>/dev/null)" fi if [ -x "$ninja_bin" ]; then ninja_version=`$ninja_bin --version 2>/dev/null` if [ $? -eq 0 ]; then if [ -d build ]; then last_ninja_version=`cat build/.ninja_version 2>/dev/null` else last_ninja_version=0 fi if [ "$ninja_version" != "$last_ninja_version" ]; then echo "-- Found Ninja: $ninja_bin (found version \"$ninja_version\")" fi cmake_type="Ninja" exec_bin="$ninja_bin" exec_command="$exec_bin" elif [ "/usr/sbin/ninja" = "$ninja_bin" ]; then echo "-- Ninja at $ninja_bin is not usable. Did you install 'ninja' instead of 'ninja-build'?" fi fi if ! [ -x "$exec_bin" ]; then make_bin="$(which make 2>/dev/null)" if [ -x "$make_bin" ]; then echo "-- Found Make: $make_bin" cmake_type="Unix Makefiles" exec_bin="$make_bin" exec_command="$exec_bin" echo "-- Running with make. Using Ninja (ninja-build) might improve build experience." fi fi if ! [ -x "$exec_bin" ]; then echo "-!- No compatible build system (Ninja, Make) found." exit 4 fi if [ -f ./build ]; then echo "-!- ./build file exists. ./configure can't continue" exit 2 fi if [ -d build ]; then last_type=`cat build/.cmake_type` if [ "$cmake_type" != "$last_type" ] then echo "-- Using different build system, cleaning build system files" cd build rm -r CMakeCache.txt CMakeFiles cd .. fi fi mkdir -p build cd build echo "$cmake_type" > .cmake_type echo "$ninja_version" > .ninja_version cmake -G "$cmake_type" \ -DCMAKE_INSTALL_PREFIX="$PREFIX" \ -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ -DENABLED_PLUGINS="$ENABLED_PLUGINS" \ -DDISABLED_PLUGINS="$DISABLED_PLUGINS" \ -DBUILD_TESTS="$BUILD_TESTS" \ -DBUILD_LIBSIGNAL_IN_TREE="$BUILD_LIBSIGNAL_IN_TREE" \ -DVALA_EXECUTABLE="$VALAC" \ -DCMAKE_VALA_FLAGS="$VALACFLAGS" \ -DDISABLE_FAST_VAPI="$DISABLE_FAST_VAPI" \ -DLIB_SUFFIX="$LIB_SUFFIX" \ -DNO_DEBUG="$NO_DEBUG" \ -DEXEC_INSTALL_PREFIX="$EXEC_PREFIX" \ -DSHARE_INSTALL_PREFIX="$DATADIR" \ -DBIN_INSTALL_DIR="$BINDIR" \ -DINCLUDE_INSTALL_DIR="$INCLUDEDIR" \ -DLIB_INSTALL_DIR="$LIBDIR" \ -Wno-dev \ .. || exit 9 if [ "$cmake_type" = "Ninja" ]; then cat << EOF > Makefile default: @sh -c "$exec_command" %: @sh -c "$exec_command \"\$@\"" EOF fi cd .. cat << EOF > Makefile default: @sh -c "cd build; $exec_command" distclean: clean uninstall test: default echo "make test not yet supported" %: @sh -c "cd build; $exec_command \"\$@\"" EOF echo "-- Configured. Type 'make' to build, 'make install' to install." dino-0.1.0/dino.doap0000644000000000000000000004460713614354364012761 0ustar rootroot Dino dino Modern XMPP Chat Client ็Žฐไปฃ XMPP ่Šๅคฉๅฎขๆˆท็ซฏ ะกะพะฒั€ะตะผะตะฝะฝั‹ะน ั‡ะฐั‚-ะบะปะธะตะฝั‚ ะฝะฐ XMPP Client XMPP de discuศ›ii modern Moderno cliente de chat XMPP Nowoczesny komunikator XMPP Modernen XMPP-chatcliรซnt Moderne XMPP-chatcliรซnt Moderne XMPP-sludreklient Modernen XMPP Chat Client ็พไปฃ็š„ใช XMPP ใƒใƒฃใƒƒใƒˆ ใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆ Client di chat moderno per XMPP Modern XMPP รœzenetkรผldล‘ Cliente moderno para parolas XMPP Client XMPP moderne XMPP txat bezero modernoa Cliente de XMPP moderno Moderner XMPP Chat Client ุชุทุจูŠู‚ ุญุฏูŠุซ ู„ู„ุฏุฑุฏุดุฉ ุนุจุฑ XMPP Dino is a modern open-source chat client for the desktop. It focuses on providing a clean and reliable Jabber/XMPP experience while having your privacy in mind. It supports end-to-end encryption with OMEMO and OpenPGP and allows configuring privacy-related features such as read receipts and typing notifications. Dino fetches history from the server and synchronizes messages with other devices. Dino ๆ˜ฏไธ€ไธช็Žฐไปฃ็š„ๅผ€ๆบ่ŠๅคฉๆกŒ้ขๅฎขๆˆท็ซฏใ€‚ๅฎƒ่‡ดๅŠ›ไบŽๆไพ›ไธ€ไธชๆธ…็ˆฝๅˆๅฏ้ ็š„ Jabber/XMPP ไฝ“้ชŒ๏ผŒๅŒๆ—ถๅˆไฟๆŠคๆ‚จ็š„้š็งใ€‚ ๅฎƒๆ”ฏๆŒ OMEMO ๅ’Œ OpenPGP ็ซฏๅฏน็ซฏๅŠ ๅฏ†ๅนถๅ…่ฎธ้…็ฝฎ้š็ง็›ธๅ…ณ็š„็‰นๆ€งๆฏ”ๅฆ‚ๅทฒ่ฏปๅ›žๆ‰งๅ’Œ่พ“ๅ…ฅๆ้†’ใ€‚ Dino ไปŽๆœๅŠกๅ™จ่Žทๅ–ๆถˆๆฏๅนถๅ’Œๅ…ถไป–่ฎพๅค‡ๅŒๆญฅใ€‚ Dino - ัั‚ะพ ัะพะฒั€ะตะผะตะฝะฝั‹ะน ะบะปะธะตะฝั‚ ะดะปั ั‡ะฐั‚ะพะฒ ั ะพั‚ะบั€ั‹ั‚ั‹ะผ ะธัั…ะพะดะฝั‹ะผ ะบะพะดะพะผ, ะฝะฐะฟั€ะฐะฒะปะตะฝะฝั‹ะน ะฝะฐ ะฝะฐะดั‘ะถะฝะพะต ะธ ะฟั€ะธะฒะฐั‚ะฝะพะต ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะต Jabber/XMPP ะฝะฐ ะฟะตั€ัะพะฝะฐะปัŒะฝั‹ั… ะบะพะผะฟัŒัŽั‚ะตั€ะฐั…. ะžะฝ ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ ัะบะฒะพะทะฝะพะต ัˆะธั„ั€ะพะฒะฐะฝะธะต ั‡ะตั€ะตะท OMEMO ะธ OpenPGP ะธ ะฟะพะทะฒะพะปัะตั‚ ะฝะฐัั‚ั€ะฐะธะฒะฐั‚ัŒ ั‚ะฐะบะธะต ั„ัƒะฝะบั†ะธะธ, ะบะฐะบ ัƒะฒะตะดะพะผะปะตะฝะธั ะพ ะฟั€ะพั‡ั‚ะตะฝะธะธ ะธ ะฝะฐะฑะพั€ะต ัะพะพะฑั‰ะตะฝะธะน. Dino ะทะฐะณั€ัƒะถะฐะตั‚ ะธัั‚ะพั€ะธัŽ ั ัะตั€ะฒะตั€ะฐ ะธ ัะธะฝั…ั€ะพะฝะธะทะธั€ัƒะตั‚ ัะพะพะฑั‰ะตะฝะธั ั ะดั€ัƒะณะธะผะธ ัƒัั‚ั€ะพะนัั‚ะฒะฐะผะธ. Dino este un client de chat modern, cu sursฤƒ deschisฤƒ, pentru calculatoare. Se concentreazฤƒ sฤƒ furnizeze o experienศ›ฤƒ Jabber/XMPP clarฤƒ ศ™i fiabilฤƒ, ศ›inรขnd cont de confidenศ›ialitatea dumneavoastrฤƒ. Suportฤƒ criptare de la un capฤƒt la altul prin intermediul OMEMO ศ™i OpenPGP, ศ™i permite configurarea caracteristicilor legate de confidenศ›ialitate precum trimiterea notificฤƒrilor de primire ศ™i tastare. Dino preia istoricul discuศ›iilor de pe server ศ™i sincronizeazฤƒ mesajele cu celelalte dispozitive. Dino รฉ um moderno chat de cรณdigo aberto para desktop. Ele รฉ focado em prover uma transparente e confiรกvel experiรชncia Jabber/XMPP, tendo em mente a sua privacidade. Suporte criptografia ponta a ponta com OMEMO e OpenPGP e permite configurar privacidadeโ€”caracterรญsticas relacionadas ร s notificaรงรตes de leitura, recebimento e escrita. Dino obtรฉm o histรณrico do servidor e sincroniza mensagens com outros dispositivos. Dino jest nowoczesnym, otwartym komunikatorem. Skupia siฤ™ na prostej obsล‚udze sieci Jabber/XMPP dbajฤ…c o twojฤ… prywatnoล›ฤ‡. Dino obsล‚uguje szyfrowanie end-to-end za pomocฤ… OMEMO i OpenPGP, a takลผe daje kontrolฤ™ย nad funkcjami wpล‚ywajฤ…cymi na prywatnoล›ฤ‡, takimi jak powiadomienia o pisaniu czy odczytaniu wiadomoล›ci. Dino pobiera historiฤ™ rozmรณw z serwera i synchronizuje wiadomoล›ci z innymi urzฤ…dzeniami. Dino is een moderne, vrije chattoepassing voor uw bureaublad. Ze biedt een eenvoudige en betrouwbare Jabber/XMPP-ervaring, met uw privacy in het achterhoofd. Ze ondersteunt eind-tot-eind-versleuteling met OMEMO en OpenPGP, en laat u toe privacygerelateerde functies, gelijk leesbevestigingen en typmeldingen, in te stellen. Dino haalt de geschiedenis op van de server en synchroniseert berichten met andere apparaten. Dino is een moderne, vrije chattoepassing voor uw bureaublad. Ze biedt een eenvoudige en betrouwbare Jabber/XMPP-ervaring, met uw privacy in het achterhoofd. Ze ondersteunt eind-tot-eind-versleuteling met OMEMO en OpenPGP, en staat u toe privacy-gerelateerde functies, zoals leesbevestigingen en typmeldingen, in te stellen. Dino haalt de geschiedenis op van de server en synchroniseert berichten met andere apparaten. Dino er en moderne friporg-sludringsklient for skrivebordet. Det fokuserer pรฅ rask og pรฅlitelig XMPP-opplevelse, samtidig som det hegner om personvernet. Det stรธtter ende-til-ende -kryptering med OMEMO og OpenPGP, og tillater oppsett av personvernsrelaterte funksjoner som meldingskvitteringer og skrivevarsling. Dino henter historikk fra tjeneren og synkroniserer meldinger med andre enheter. Dino assย e modernen, quell-offeneย Chat Client fir den Desktop. Hien biet eng opgeraumtย a robust Jabber/XMPP Erfarungย a leet ee Schwรฉierpunkt op Privatsphรคr. Hien รซnnerstรซtzย Enn-zu-Enn Verschlรซsselungย mat OMEMO an OpenPGP an enthรคlt Privatsphรคre-Astellungen zu Liesbestรคtegungenย an Tipp-Benoriichtegungen. Dino rifftย  Gesprรฉichverlรคf vum Server of a synchronisรฉiert Noriichteย mat anereย Gerรคter. Dino รจ un client di chat per il desktop, moderno e open-source. Si concentra nel fornire un'esperienza Jabber/XMPP pulita e affidabile tenendo presente la tua privacy. Support la crittografia end-to-end tramite OMEMO e OpenPGP e permette di configurare le funzioni relative alla privacy come le ricevute di lettura e le notifiche di digitazione. Dino recupera la cronologia dal server e sincronizza i messaggi con gli altri dispositivi. A Dino egy modern, nyรญlt forrรกskรณdรบ รผzenetkรผldล‘ alkalmazรกs asztali rendszerekre, ami a hangsรบlyt a letisztult รฉs megbรญzhatรณ Jabber/XMPP รฉlmรฉnyre helyezi, mikรถzben a magรกnszfรฉra megล‘rzรฉsรฉt is fontosnak tartja. Tรกmogatja a vรฉgponttรณl-vรฉgpontig titkosรญtรกst az OMEMO รฉs az OpenPGP รกltal, รฉs magรกnszfรฉrรกhoz kรถtล‘dล‘ beรกllรญtรกsi lehetล‘sรฉgeket is biztosรญt, mint pรฉldรกul a kรฉzbesรญtรฉsi, vagy gรฉpelรฉsi รฉrtesรญtรฉsek kรผldรฉse. A Dino lekรฉri a chat elล‘zmรฉnyeket a szerverrล‘l, รฉs szinkronizรกlja az รผzeneteket a tรถbbi eszkรถzzel. Dino รฉ un cliente moderno e de cรณdigo aberto para o escritorio. Orientado a fornecer unha experiencia Jabber/XMPP limpa e fiรกbel tendo a privacidade e seguranza presentes. Suporta o cifrado de punto-a-punto con OMEMO e OpenPGP e permite configurar trazos orientados รก privacidade tales coma confirmaciรณn de lectura e notificaciรณns de escritura. Dino obtรฉn o histรณrico dende o servidor e sincroniza as mensaxes con outros dispositivos. Dino est un client de chat libre et moderne pour le bureau. Il tente de fournir une expรฉrience Jabber/XMPP simple et fiable tout en ayant toujours ร  lโ€™esprit votre vie privรฉe. Il prend en charge le chiffrement de bout en bout avec OMEMO et OpenPGP et permet de configurer les fonctions liรฉes ร  la vie privรฉe telles que lโ€™accusรฉ de rรฉception et les notifications de frappe. Dino rรฉcupรจre lโ€™historique du serveur et synchronise les messages avec d'autres appareils. Dino on nykyaikainen avoimen lรคhdekoodin jutteluohjelma tyรถpรถydรคlle. Se keskittyy tarjoamaan selkeรคn ja luotettavan Jabber/XMPP-kokemuksen unohtamatta yksityisyyttรคsi. Se tukee pรครคstรค pรครคhรคn -salausta OMEMO:n ja OpenPGP:n avulla ja mahdollistaa yksityisyyteen liittyvien ominaisuuksien, kuten lukukuittausten ja kirjoitusilmoitusten asetusten mรครคrittรคmisen. Dino hakee historian palvelimelta ja synkronisoi viestit muiden laitteiden kanssa. Dino mahaigainerako iturburu irekiko txat bezero moderno bat da. Jabber/XMPP esperientzia garbi eta fidagarri bat ematen du zure pribatutasuna kontuan hartzeaz gain. Amaieratik amaierarako enkriptazioa onartzen du OMEMO eta OpenPGPrekin eta pribatutasun ezaugarriak konfiguratzea baimentzen du irakurtze markak eta idazketa jakinarazpenak bezala. Dinok zerbitzaritik hartzen du historia eta beste gailuekin mezuak sinkronizatzen ditu. Dino es un cliente de mensajerรญa moderno y libre para escritorio. Estรก enfocado en proveer una experiencia Jabber/XMPP limpia y confiable teniendo tu privacidad en mente. Soporta cifrado de extremo a extremo a travรฉs de OMEMO y OpenPGP y permite configurar las caracterรญsticas relacionadas con la privacidad, como confirmaciones de lectura y notificaciones de escritura. Dino recupera los mensajes desde el servidor y sincroniza los mensajes con otros dispositivos. Dino ist ein moderner, quelloffener Chat Client. Er bietet eine aufgerรคumte und robuste Jabber/XMPP Erfahrung und legt einen Schwerpunkt auf Privatsphรคre. Er unterstรผtzt Ende-zu-Ende Verschlรผsselung mit OMEMO und OpenPGP und enthรคlt Privatsphรคre-Einstellungen zu Lesebestรคtigungen und Tippbenachrichtigungen. Dino ruft Gesprรคchsverlรคufe vom Server ab und synchronisiert Nachrichten mit anderen Gerรคten. Dino รฉs un client de xat lliure i modern per a l'escriptori. Estร  centrat en proveir una experiรจncia neta i fiable de Jabber/XMPP, sempre tenint en compte la vostra privacitat. Implementa xifratge punt a punt amb OMEMO i OpenPGP, i permet configurar funcionalitats relacionades amb la privacitat com per exemple rebuts de lectura i notificacions d'escriptura. Dino recupera l'historial del servidor i sincronitza els missatges amb altres dispositius. ุฏูŠู†ูˆ ุจุฑู†ุงู…ุฌ ุญุฏูŠุซ ูˆู…ูุชูˆุญ ุงู„ู…ุตุฏุฑ ู„ู„ุฏุฑุฏุดุฉ ุตูู…ู‘ู… ู„ุณุทุญ ุงู„ู…ูƒุชุจ. ูˆูŠูุฑูƒู‘ุฒ ุนู„ูŠ ุชู‚ุฏูŠู… ุชุฌุฑุจุฉ ู†ุธูŠูุฉ ูˆู…ูˆุซูˆู‚ ู…ู†ู‡ุง ู„ุฌุงุจุฑ/XMPP ู…ุน ุฃุฎุฐ ุฎุตูˆุตูŠุชูƒู… ุจุนูŠู† ุงู„ุฅุนุชุจุงุฑ. ูˆู‡ูˆ ูŠุฏุนู… ุงู„ุชุดููŠุฑ ุจูˆุงุณุทุฉ OMEMO ูˆ OpenPGP ูŠุณู…ุญ ุจุฅุนุฏุงุฏ ู…ูŠุฒุงุช ุงู„ุฎุตูˆุตูŠุฉ ูƒุงู„ุฅูŠุตุงู„ุงุช ุงู„ู…ู‚ุฑูˆุกุฉ ูˆุงู„ุฅุฎุทุงุฑุงุช ุนู†ุฏ ุงู„ูƒุชุงุจุฉ. ูŠู‚ูˆู… ุฏูŠู†ูˆ ุจุฌู„ุจ ุงู„ุณูุฌู„ู‘ ู…ูู† ุงู„ุณูŠุฑูุฑ ุซู… ูŠูุฒุงู…ูู† ุงู„ุฑุณุงุฆู„ ู…ุน ุงู„ุฃุฌู‡ุฒุฉ ุงู„ุฃุฎุฑู‰. Vala Linux FreeBSD complete complete complete partial complete partial Only for viewing avatars partial complete complete complete complete complete complete partial complete partial complete complete complete complete partial Not for MUCs complete complete complete partial Only for outgoing messages complete dino-0.1.0/libdino/0000755000000000000000000000000013614354364012570 5ustar rootrootdino-0.1.0/libdino/CMakeLists.txt0000644000000000000000000000530513614354364015333 0ustar rootrootfind_packages(LIBDINO_PACKAGES REQUIRED GDKPixbuf2 Gee GLib GModule GObject ) vala_precompile(LIBDINO_VALA_C SOURCES src/application.vala src/dbus/login1.vala src/dbus/upower.vala src/entity/account.vala src/entity/conversation.vala src/entity/encryption.vala src/entity/file_transfer.vala src/entity/message.vala src/entity/settings.vala src/plugin/interfaces.vala src/plugin/loader.vala src/plugin/registry.vala src/service/avatar_manager.vala src/service/avatar_storage.vala src/service/blocking_manager.vala src/service/chat_interaction.vala src/service/connection_manager.vala src/service/content_item_store.vala src/service/conversation_manager.vala src/service/counterpart_interaction_manager.vala src/service/database.vala src/service/entity_capabilities_storage.vala src/service/file_manager.vala src/service/jingle_file_transfers.vala src/service/message_processor.vala src/service/message_storage.vala src/service/module_manager.vala src/service/muc_manager.vala src/service/notification_events.vala src/service/presence_manager.vala src/service/registration.vala src/service/roster_manager.vala src/service/search_processor.vala src/service/stream_interactor.vala src/service/util.vala src/util.vala CUSTOM_VAPIS "${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi" "${CMAKE_BINARY_DIR}/exports/qlite.vapi" CUSTOM_DEPS xmpp-vala qlite PACKAGES ${LIBDINO_PACKAGES} GENERATE_VAPI dino GENERATE_HEADER dino ) add_custom_command(OUTPUT "${CMAKE_BINARY_DIR}/exports/dino_i18n.h" COMMAND cp "${CMAKE_CURRENT_SOURCE_DIR}/src/dino_i18n.h" "${CMAKE_BINARY_DIR}/exports/dino_i18n.h" DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/dino_i18n.h" COMMENT Copy header file dino_i18n.h ) add_custom_target(dino-vapi DEPENDS ${CMAKE_BINARY_DIR}/exports/dino.vapi ${CMAKE_BINARY_DIR}/exports/dino.deps ${CMAKE_BINARY_DIR}/exports/dino_i18n.h ) add_definitions(${VALA_CFLAGS} -DDINO_SYSTEM_PLUGIN_DIR="${PLUGIN_INSTALL_DIR}" -DDINO_SYSTEM_LIBDIR_NAME="${LIBDIR_NAME}" -DG_LOG_DOMAIN="libdino") add_library(libdino SHARED ${LIBDINO_VALA_C} ${CMAKE_BINARY_DIR}/exports/dino_i18n.h) add_dependencies(libdino dino-vapi) target_link_libraries(libdino xmpp-vala qlite ${LIBDINO_PACKAGES} m) set_target_properties(libdino PROPERTIES PREFIX "" VERSION 0.0 SOVERSION 0) install(TARGETS libdino ${TARGET_INSTALL}) install(FILES ${CMAKE_BINARY_DIR}/exports/dino.vapi ${CMAKE_BINARY_DIR}/exports/dino.deps DESTINATION ${VAPI_INSTALL_DIR}) install(FILES ${CMAKE_BINARY_DIR}/exports/dino.h ${CMAKE_BINARY_DIR}/exports/dino_i18n.h DESTINATION ${INCLUDE_INSTALL_DIR}) dino-0.1.0/libdino/src/0000755000000000000000000000000013614354364013357 5ustar rootrootdino-0.1.0/libdino/src/application.vala0000644000000000000000000001272413614354364016535 0ustar rootrootusing Dino.Entities; namespace Dino { extern const string VERSION; public interface Application : GLib.Application { public abstract Database db { get; set; } public abstract Dino.Entities.Settings settings { get; set; } public abstract StreamInteractor stream_interactor { get; set; } public abstract Plugins.Registry plugin_registry { get; set; } public abstract SearchPathGenerator? search_path_generator { get; set; } internal static string print_xmpp; private const OptionEntry[] options = { { "print-xmpp", 0, 0, OptionArg.STRING, ref print_xmpp, "Print XMPP stanzas identified by DESC to stderr", "DESC" }, { null } }; public abstract void handle_uri(string jid, string query, Gee.Map options); public void init() throws Error { if (DirUtils.create_with_parents(get_storage_dir(), 0700) == -1) { throw new Error(-1, 0, "Could not create storage dir \"%s\": %s", get_storage_dir(), FileUtils.error_from_errno(errno).to_string()); } this.db = new Database(Path.build_filename(get_storage_dir(), "dino.db")); this.settings = new Dino.Entities.Settings.from_db(db); this.stream_interactor = new StreamInteractor(db); MessageProcessor.start(stream_interactor, db); MessageStorage.start(stream_interactor, db); CounterpartInteractionManager.start(stream_interactor); PresenceManager.start(stream_interactor); BlockingManager.start(stream_interactor); ConversationManager.start(stream_interactor, db); MucManager.start(stream_interactor); AvatarManager.start(stream_interactor, db); RosterManager.start(stream_interactor, db); ChatInteraction.start(stream_interactor); FileManager.start(stream_interactor, db); ContentItemStore.start(stream_interactor, db); NotificationEvents.start(stream_interactor); SearchProcessor.start(stream_interactor, db); Register.start(stream_interactor, db); create_actions(); startup.connect(() => { stream_interactor.connection_manager.log_options = print_xmpp; Idle.add(() => { restore(); return false; }); }); shutdown.connect(() => { stream_interactor.connection_manager.make_offline_all(); }); open.connect((files, hint) => { if (files.length != 1) { warning("Can't handle more than one URI at once."); return; } File file = files[0]; if (!file.has_uri_scheme("xmpp")) { warning("xmpp:-URI expected"); return; } string uri = file.get_uri(); if (!uri.contains(":")) { warning("Invalid URI"); return; } string r = uri.split(":", 2)[1]; string[] m = r.split("?", 2); string jid = m[0]; while (jid[0] == '/') { jid = jid.substring(1); } jid = Uri.unescape_string(jid); try { jid = new Xmpp.Jid(jid).to_string(); } catch (Xmpp.InvalidJidError e) { warning("Received invalid jid in xmpp:-URI: %s", e.message); } string query = "message"; Gee.Map options = new Gee.HashMap(); if (m.length == 2) { string[] cmds = m[1].split(";"); query = cmds[0]; for (int i = 1; i < cmds.length; ++i) { string[] opt = cmds[i].split("=", 2); options[Uri.unescape_string(opt[0])] = opt.length == 2 ? Uri.unescape_string(opt[1]) : ""; } } activate(); handle_uri(jid, query, options); }); add_main_option_entries(options); } public static string get_storage_dir() { return Path.build_filename(Environment.get_user_data_dir(), "dino"); } public static unowned Application get_default() { return (Dino.Application) GLib.Application.get_default(); } public void create_actions() { SimpleAction accept_subscription_action = new SimpleAction("accept-subscription", VariantType.INT32); accept_subscription_action.activate.connect((variant) => { Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_by_id(variant.get_int32()); if (conversation == null) return; stream_interactor.get_module(PresenceManager.IDENTITY).approve_subscription(conversation.account, conversation.counterpart); stream_interactor.get_module(PresenceManager.IDENTITY).request_subscription(conversation.account, conversation.counterpart); }); add_action(accept_subscription_action); } protected void add_connection(Account account) { if ((get_flags() & ApplicationFlags.IS_SERVICE) == ApplicationFlags.IS_SERVICE) hold(); stream_interactor.connect_account(account); } protected void remove_connection(Account account) { if ((get_flags() & ApplicationFlags.IS_SERVICE) == ApplicationFlags.IS_SERVICE) release(); stream_interactor.disconnect_account.begin(account); } private void restore() { foreach (Account account in db.get_accounts()) { if (account.enabled) add_connection(account); } } } }dino-0.1.0/libdino/src/dbus/0000755000000000000000000000000013614354364014314 5ustar rootrootdino-0.1.0/libdino/src/dbus/login1.vala0000644000000000000000000000071113614354364016351 0ustar rootrootnamespace Dino { [DBus (name = "org.freedesktop.login1.Manager")] public interface Login1Manager : Object { public signal void PrepareForSleep(bool suspend); } public static Login1Manager? get_login1() { Login1Manager? login1 = null; try { login1 = Bus.get_proxy_sync(BusType.SYSTEM, "org.freedesktop.login1", "/org/freedesktop/login1"); } catch (IOError e) { stderr.printf("%s\n", e.message); } return login1; } }dino-0.1.0/libdino/src/dbus/upower.vala0000644000000000000000000000067513614354364016512 0ustar rootrootnamespace Dino { [DBus (name = "org.freedesktop.UPower")] public interface UPower : Object { public signal void Sleeping(); public signal void Resuming(); } public static UPower? get_upower() { UPower? upower = null; try { upower = Bus.get_proxy_sync(BusType.SYSTEM, "org.freedesktop.UPower", "/org/freedesktop/UPower"); } catch (IOError e) { stderr.printf ("%s\n", e.message); } return upower; } }dino-0.1.0/libdino/src/dino_i18n.h0000644000000000000000000000040513614354364015317 0ustar rootroot#ifndef __DINO_I18N_H__ #define __DINO_I18N_H__ #include #define dino_gettext(MsgId) ((char *) dgettext (GETTEXT_PACKAGE, MsgId)) #define dino_ngettext(MsgId, MsgIdPlural, Int) ((char *) dngettext (GETTEXT_PACKAGE, MsgId, MsgIdPlural, Int)) #endifdino-0.1.0/libdino/src/entity/0000755000000000000000000000000013614354364014673 5ustar rootrootdino-0.1.0/libdino/src/entity/account.vala0000644000000000000000000001031513614354364017174 0ustar rootrootusing Gee; using Xmpp; namespace Dino.Entities { public class Account : Object { public int id { get; set; } public string localpart { get { return full_jid.localpart; } } public string domainpart { get { return full_jid.domainpart; } } public string resourcepart { get { return full_jid.resourcepart;} } public Jid bare_jid { owned get { return full_jid.bare_jid; } } public Jid full_jid { get; private set; } public string? password { get; set; } public string display_name { owned get { return alias ?? bare_jid.to_string(); } } public string? alias { get; set; } public bool enabled { get; set; default = false; } public string? roster_version { get; set; } public DateTime mam_earliest_synced { get; set; default=new DateTime.from_unix_utc(0); } private Database? db; public Account(Jid bare_jid, string? resourcepart, string? password, string? alias) { this.id = -1; if (resourcepart != null) { try { this.full_jid = bare_jid.with_resource(resourcepart); } catch (InvalidJidError e) { warning("Tried to create account with invalid resource (%s), defaulting to auto generated", e.message); } } if (this.full_jid == null) { try { this.full_jid = bare_jid.with_resource("dino." + Random.next_int().to_string("%x")); } catch (InvalidJidError e) { error("Auto-generated resource was invalid (%s)", e.message); } } this.password = password; this.alias = alias; } public Account.from_row(Database db, Qlite.Row row) throws InvalidJidError { this.db = db; id = row[db.account.id]; full_jid = new Jid(row[db.account.bare_jid]).with_resource(row[db.account.resourcepart]); password = row[db.account.password]; alias = row[db.account.alias]; enabled = row[db.account.enabled]; roster_version = row[db.account.roster_version]; mam_earliest_synced = new DateTime.from_unix_utc(row[db.account.mam_earliest_synced]); notify.connect(on_update); } public void persist(Database db) { if (id > 0) return; this.db = db; id = (int) db.account.insert() .value(db.account.bare_jid, bare_jid.to_string()) .value(db.account.resourcepart, resourcepart) .value(db.account.password, password) .value(db.account.alias, alias) .value(db.account.enabled, enabled) .value(db.account.roster_version, roster_version) .value(db.account.mam_earliest_synced, (long)mam_earliest_synced.to_unix()) .perform(); notify.connect(on_update); } public void remove() { db.account.delete().with(db.account.bare_jid, "=", bare_jid.to_string()).perform(); notify.disconnect(on_update); id = -1; db = null; } public bool equals(Account acc) { return equals_func(this, acc); } public static bool equals_func(Account acc1, Account acc2) { return acc1.bare_jid.to_string() == acc2.bare_jid.to_string(); } public static uint hash_func(Account acc) { return acc.bare_jid.to_string().hash(); } private void on_update(Object o, ParamSpec sp) { var update = db.account.update().with(db.account.id, "=", id); switch (sp.name) { case "bare-jid": update.set(db.account.bare_jid, bare_jid.to_string()); break; case "resourcepart": update.set(db.account.resourcepart, resourcepart); break; case "password": update.set(db.account.password, password); break; case "alias": update.set(db.account.alias, alias); break; case "enabled": update.set(db.account.enabled, enabled); break; case "roster-version": update.set(db.account.roster_version, roster_version); break; case "mam-earliest-synced": update.set(db.account.mam_earliest_synced, (long)mam_earliest_synced.to_unix()); break; } update.perform(); } } } dino-0.1.0/libdino/src/entity/conversation.vala0000644000000000000000000001664613614354364020267 0ustar rootrootusing Xmpp; namespace Dino.Entities { public class Conversation : Object { public signal void object_updated(Conversation conversation); public enum Type { CHAT, GROUPCHAT, GROUPCHAT_PM; public bool is_muc_semantic() { return this == GROUPCHAT || this == GROUPCHAT_PM; } } public int id { get; set; } public Type type_ { get; set; } public Account account { get; private set; } public Jid counterpart { get; private set; } public string? nickname { get; set; } public bool active { get; set; default = false; } private DateTime? _last_active; public DateTime? last_active { get { return _last_active; } set { if (_last_active == null || (value != null && value.difference(_last_active) > 0)) { _last_active = value; } } } public Encryption encryption { get; set; default = Encryption.NONE; } public Message? read_up_to { get; set; } public enum NotifySetting { DEFAULT, ON, OFF, HIGHLIGHT } public NotifySetting notify_setting { get; set; default = NotifySetting.DEFAULT; } public enum Setting { DEFAULT, ON, OFF } public Setting send_typing { get; set; default = Setting.DEFAULT; } public Setting send_marker { get; set; default = Setting.DEFAULT; } private Database? db; public Conversation(Jid jid, Account account, Type type) { this.account = account; this.counterpart = jid; this.type_ = type; } public Conversation.from_row(Database db, Qlite.Row row) throws InvalidJidError { this.db = db; id = row[db.conversation.id]; type_ = (Conversation.Type) row[db.conversation.type_]; account = db.get_account_by_id(row[db.conversation.account_id]); string? resource = row[db.conversation.resource]; counterpart = db.get_jid_by_id(row[db.conversation.jid_id]); if (type_ == Conversation.Type.GROUPCHAT_PM) counterpart = counterpart.with_resource(resource); nickname = type_ == Conversation.Type.GROUPCHAT ? resource : null; active = row[db.conversation.active]; int64? last_active = row[db.conversation.last_active]; if (last_active != null) this.last_active = new DateTime.from_unix_utc(last_active); encryption = (Encryption) row[db.conversation.encryption]; int? read_up_to = row[db.conversation.read_up_to]; if (read_up_to != null) this.read_up_to = db.get_message_by_id(read_up_to); notify_setting = (NotifySetting) row[db.conversation.notification]; send_typing = (Setting) row[db.conversation.send_typing]; send_marker = (Setting) row[db.conversation.send_marker]; notify.connect(on_update); } public void persist(Database db) { this.db = db; var insert = db.conversation.insert() .value(db.conversation.account_id, account.id) .value(db.conversation.jid_id, db.get_jid_id(counterpart)) .value(db.conversation.type_, type_) .value(db.conversation.encryption, encryption) .value(db.conversation.active, active) .value(db.conversation.notification, notify_setting) .value(db.conversation.send_typing, send_typing) .value(db.conversation.send_marker, send_marker); if (read_up_to != null) { insert.value(db.conversation.read_up_to, read_up_to.id); } if (nickname != null) { insert.value(db.conversation.resource, nickname); } if (counterpart.is_full()) { insert.value(db.conversation.resource, counterpart.resourcepart); } if (last_active != null) { insert.value(db.conversation.last_active, (long) last_active.to_unix()); } id = (int) insert.perform(); notify.connect(on_update); } public NotifySetting get_notification_setting(StreamInteractor stream_interactor) { return notify_setting != NotifySetting.DEFAULT ? notify_setting : get_notification_default_setting(stream_interactor); } public NotifySetting get_notification_default_setting(StreamInteractor stream_interactor) { if (!Application.get_default().settings.notifications) return NotifySetting.OFF; if (type_ == Type.GROUPCHAT) { if (stream_interactor.get_module(MucManager.IDENTITY).is_private_room(this.account, this.counterpart)) { return NotifySetting.ON; } else { return NotifySetting.HIGHLIGHT; } } return NotifySetting.ON; } public Setting get_send_typing_setting(StreamInteractor stream_interactor) { if (send_typing != Setting.DEFAULT) return send_typing; if (stream_interactor.get_module(MucManager.IDENTITY).is_public_room(this.account, this.counterpart)) return Setting.OFF; return Application.get_default().settings.send_typing ? Setting.ON : Setting.OFF; } public Setting get_send_marker_setting(StreamInteractor stream_interactor) { if (send_marker != Setting.DEFAULT) return send_marker; if (stream_interactor.get_module(MucManager.IDENTITY).is_public_room(this.account, this.counterpart)) return Setting.OFF; return Application.get_default().settings.send_marker ? Setting.ON : Setting.OFF; } public bool equals(Conversation? conversation) { if (conversation == null) return false; return equals_func(this, conversation); } public static bool equals_func(Conversation conversation1, Conversation conversation2) { return conversation1.counterpart.equals(conversation2.counterpart) && conversation1.account.equals(conversation2.account) && conversation1.type_ == conversation2.type_; } public static uint hash_func(Conversation conversation) { return conversation.counterpart.to_string().hash() ^ conversation.account.bare_jid.to_string().hash(); } private void on_update(Object o, ParamSpec sp) { var update = db.conversation.update().with(db.conversation.id, "=", id); switch (sp.name) { case "type-": update.set(db.conversation.type_, type_); break; case "encryption": update.set(db.conversation.encryption, encryption); break; case "read-up-to": if (read_up_to != null) { update.set(db.conversation.read_up_to, read_up_to.id); } else { update.set_null(db.conversation.read_up_to); } break; case "nickname": update.set(db.conversation.resource, nickname); break; case "active": update.set(db.conversation.active, active); break; case "last-active": if (last_active != null) { update.set(db.conversation.last_active, (long) last_active.to_unix()); } else { update.set_null(db.conversation.last_active); } break; case "notify-setting": update.set(db.conversation.notification, notify_setting); break; case "send-typing": update.set(db.conversation.send_typing, send_typing); break; case "send-marker": update.set(db.conversation.send_marker, send_marker); break; } update.perform(); } } } dino-0.1.0/libdino/src/entity/encryption.vala0000644000000000000000000000014113614354364017726 0ustar rootrootnamespace Dino.Entities { public enum Encryption { NONE, PGP, OMEMO } }dino-0.1.0/libdino/src/entity/file_transfer.vala0000644000000000000000000001502613614354364020367 0ustar rootrootusing Xmpp; namespace Dino.Entities { public class FileTransfer : Object { public const bool DIRECTION_SENT = true; public const bool DIRECTION_RECEIVED = false; public enum State { COMPLETE, IN_PROGRESS, NOT_STARTED, FAILED } public int id { get; set; default=-1; } public Account account { get; set; } public Jid counterpart { get; set; } public Jid ourpart { get; set; } public Jid? from { get { return direction == DIRECTION_SENT ? ourpart : counterpart; } } public Jid? to { get { return direction == DIRECTION_SENT ? counterpart : ourpart; } } public bool direction { get; set; } public DateTime time { get; set; } public DateTime? local_time { get; set; } public Encryption encryption { get; set; default=Encryption.NONE; } private InputStream? input_stream_ = null; public InputStream input_stream { get { if (input_stream_ == null) { File file = File.new_for_path(Path.build_filename(storage_dir, path ?? file_name)); try { input_stream_ = file.read(); } catch (Error e) { } } return input_stream_; } set { input_stream_ = value; } } public string file_name { get; set; } private string? server_file_name_ = null; public string server_file_name { get { return server_file_name_ ?? file_name; } set { server_file_name_ = value; } } public string path { get; set; } public string? mime_type { get; set; } // TODO(hrxi): expand to 64 bit public int size { get; set; default=-1; } public State state { get; set; default=State.NOT_STARTED; } public int provider { get; set; } public string info { get; set; } private Database? db; private string storage_dir; public FileTransfer.from_row(Database db, Qlite.Row row, string storage_dir) throws InvalidJidError { this.db = db; this.storage_dir = storage_dir; id = row[db.file_transfer.id]; account = db.get_account_by_id(row[db.file_transfer.account_id]); // TODO donโ€™t have to generate acc new counterpart = db.get_jid_by_id(row[db.file_transfer.counterpart_id]); string counterpart_resource = row[db.file_transfer.counterpart_resource]; if (counterpart_resource != null) counterpart = counterpart.with_resource(counterpart_resource); string our_resource = row[db.file_transfer.our_resource]; if (our_resource != null) { ourpart = account.bare_jid.with_resource(our_resource); } else { ourpart = account.bare_jid; } direction = row[db.file_transfer.direction]; time = new DateTime.from_unix_utc(row[db.file_transfer.time]); local_time = new DateTime.from_unix_utc(row[db.file_transfer.local_time]); encryption = (Encryption) row[db.file_transfer.encryption]; file_name = row[db.file_transfer.file_name]; path = row[db.file_transfer.path]; mime_type = row[db.file_transfer.mime_type]; size = row[db.file_transfer.size]; state = (State) row[db.file_transfer.state]; provider = row[db.file_transfer.provider]; info = row[db.file_transfer.info]; notify.connect(on_update); } public void persist(Database db) { if (id != -1) return; this.db = db; Qlite.InsertBuilder builder = db.file_transfer.insert() .value(db.file_transfer.account_id, account.id) .value(db.file_transfer.counterpart_id, db.get_jid_id(counterpart)) .value(db.file_transfer.counterpart_resource, counterpart.resourcepart) .value(db.file_transfer.our_resource, ourpart.resourcepart) .value(db.file_transfer.direction, direction) .value(db.file_transfer.time, (long) time.to_unix()) .value(db.file_transfer.local_time, (long) local_time.to_unix()) .value(db.file_transfer.encryption, encryption) .value(db.file_transfer.file_name, file_name) .value(db.file_transfer.size, size) .value(db.file_transfer.state, state) .value(db.file_transfer.provider, provider) .value(db.file_transfer.info, info); if (file_name != null) builder.value(db.file_transfer.file_name, file_name); if (path != null) builder.value(db.file_transfer.path, path); if (mime_type != null) builder.value(db.file_transfer.mime_type, mime_type); id = (int) builder.perform(); notify.connect(on_update); } public File get_file() { return File.new_for_path(Path.build_filename(Dino.get_storage_dir(), "files", path)); } private void on_update(Object o, ParamSpec sp) { Qlite.UpdateBuilder update_builder = db.file_transfer.update().with(db.file_transfer.id, "=", id); switch (sp.name) { case "counterpart": update_builder.set(db.file_transfer.counterpart_id, db.get_jid_id(counterpart)); update_builder.set(db.file_transfer.counterpart_resource, counterpart.resourcepart); break; case "ourpart": update_builder.set(db.file_transfer.our_resource, ourpart.resourcepart); break; case "direction": update_builder.set(db.file_transfer.direction, direction); break; case "time": update_builder.set(db.file_transfer.time, (long) time.to_unix()); break; case "local-time": update_builder.set(db.file_transfer.local_time, (long) local_time.to_unix()); break; case "encryption": update_builder.set(db.file_transfer.encryption, encryption); break; case "file-name": update_builder.set(db.file_transfer.file_name, file_name); break; case "path": update_builder.set(db.file_transfer.path, path); break; case "mime-type": update_builder.set(db.file_transfer.mime_type, mime_type); break; case "size": update_builder.set(db.file_transfer.size, size); break; case "state": if (state == State.IN_PROGRESS) return; update_builder.set(db.file_transfer.state, state); break; case "provider": update_builder.set(db.file_transfer.provider, provider); break; case "info": update_builder.set(db.file_transfer.info, info); break; } update_builder.perform(); } } } dino-0.1.0/libdino/src/entity/message.vala0000644000000000000000000001605113614354364017167 0ustar rootrootusing Gee; using Xmpp; namespace Dino.Entities { public class Message : Object { public const bool DIRECTION_SENT = true; public const bool DIRECTION_RECEIVED = false; public enum Marked { NONE, RECEIVED, READ, ACKNOWLEDGED, UNSENT, WONTSEND } public enum Type { ERROR, CHAT, GROUPCHAT, GROUPCHAT_PM, UNKNOWN; public bool is_muc_semantic() { return this == GROUPCHAT || this == GROUPCHAT_PM; } } public int id { get; set; default = -1; } public Account account { get; set; } public Jid? counterpart { get; set; } public Jid? ourpart { get; set; } public Jid? from { get { return direction == DIRECTION_SENT ? ourpart : counterpart; } } public Jid? to { get { return direction == DIRECTION_SENT ? counterpart : ourpart; } } public bool direction { get; set; } public Jid? real_jid { get; set; } public Type type_ { get; set; default = Type.UNKNOWN; } public string? body { get; set; } public string? stanza_id { get; set; } public string? server_id { get; set; } public DateTime? time { get; set; } /** UTC **/ public DateTime? local_time { get; set; } public Encryption encryption { get; set; default = Encryption.NONE; } private Marked marked_ = Marked.NONE; public Marked marked { get { return marked_; } set { if (value == Marked.RECEIVED && marked == Marked.READ) return; marked_ = value; } } private Database? db; public Message(string? body) { this.body = body; } public Message.from_row(Database db, Qlite.Row row) throws InvalidJidError { this.db = db; id = row[db.message.id]; account = db.get_account_by_id(row[db.message.account_id]); stanza_id = row[db.message.stanza_id]; server_id = row[db.message.server_id]; type_ = (Message.Type) row[db.message.type_]; counterpart = db.get_jid_by_id(row[db.message.counterpart_id]); string counterpart_resource = row[db.message.counterpart_resource]; if (counterpart_resource != null) counterpart = counterpart.with_resource(counterpart_resource); string our_resource = row[db.message.our_resource]; if (type_.is_muc_semantic() && our_resource != null) { ourpart = counterpart.with_resource(our_resource); } else if (our_resource != null) { ourpart = account.bare_jid.with_resource(our_resource); } else { ourpart = account.bare_jid; } direction = row[db.message.direction]; time = new DateTime.from_unix_utc(row[db.message.time]); local_time = new DateTime.from_unix_utc(row[db.message.local_time]); body = row[db.message.body]; marked = (Message.Marked) row[db.message.marked]; encryption = (Encryption) row[db.message.encryption]; string? real_jid_str = row[db.real_jid.real_jid]; if (real_jid_str != null) real_jid = new Jid(real_jid_str); notify.connect(on_update); } public void persist(Database db) { if (id != -1) return; this.db = db; Qlite.InsertBuilder builder = db.message.insert() .value(db.message.account_id, account.id) .value(db.message.counterpart_id, db.get_jid_id(counterpart)) .value(db.message.counterpart_resource, counterpart.resourcepart) .value(db.message.our_resource, ourpart.resourcepart) .value(db.message.direction, direction) .value(db.message.type_, type_) .value(db.message.time, (long) time.to_unix()) .value(db.message.local_time, (long) local_time.to_unix()) .value(db.message.body, body) .value(db.message.encryption, encryption) .value(db.message.marked, marked); if (stanza_id != null) builder.value(db.message.stanza_id, stanza_id); if (server_id != null) builder.value(db.message.server_id, server_id); id = (int) builder.perform(); if (real_jid != null) { db.real_jid.insert() .value(db.real_jid.message_id, id) .value(db.real_jid.real_jid, real_jid.to_string()) .perform(); } notify.connect(on_update); } public void set_type_string(string type) { switch (type) { case Xmpp.MessageStanza.TYPE_CHAT: type_ = Type.CHAT; break; case Xmpp.MessageStanza.TYPE_GROUPCHAT: type_ = Type.GROUPCHAT; break; } } public new string get_type_string() { switch (type_) { case Type.CHAT: return Xmpp.MessageStanza.TYPE_CHAT; case Type.GROUPCHAT: return Xmpp.MessageStanza.TYPE_GROUPCHAT; default: return Xmpp.MessageStanza.TYPE_NORMAL; } } public bool equals(Message? m) { if (m == null) return false; return equals_func(this, m); } public static bool equals_func(Message m1, Message m2) { if (m1.stanza_id == m2.stanza_id && m1.body == m2.body) { return true; } return false; } public static uint hash_func(Message message) { return message.body.hash(); } private void on_update(Object o, ParamSpec sp) { Qlite.UpdateBuilder update_builder = db.message.update().with(db.message.id, "=", id); switch (sp.name) { case "stanza-id": update_builder.set(db.message.stanza_id, stanza_id); break; case "server-id": update_builder.set(db.message.server_id, server_id); break; case "counterpart": update_builder.set(db.message.counterpart_id, db.get_jid_id(counterpart)); update_builder.set(db.message.counterpart_resource, counterpart.resourcepart); break; case "ourpart": update_builder.set(db.message.our_resource, ourpart.resourcepart); break; case "direction": update_builder.set(db.message.direction, direction); break; case "type-": update_builder.set(db.message.type_, type_); break; case "time": update_builder.set(db.message.time, (long) time.to_unix()); break; case "local-time": update_builder.set(db.message.local_time, (long) local_time.to_unix()); break; case "body": update_builder.set(db.message.body, body); break; case "encryption": update_builder.set(db.message.encryption, encryption); break; case "marked": update_builder.set(db.message.marked, marked); break; } update_builder.perform(); if (sp.get_name() == "real-jid") { db.real_jid.insert().or("REPLACE") .value(db.real_jid.message_id, id) .value(db.real_jid.real_jid, real_jid.to_string()) .perform(); } } } } dino-0.1.0/libdino/src/entity/settings.vala0000644000000000000000000000365213614354364017406 0ustar rootrootnamespace Dino.Entities { public class Settings : Object { private Database db; public Settings.from_db(Database db) { this.db = db; send_typing_ = col_to_bool_or_default("send_typing", true); send_marker_ = col_to_bool_or_default("send_marker", true); notifications_ = col_to_bool_or_default("notifications", true); convert_utf8_smileys_ = col_to_bool_or_default("convert_utf8_smileys", true); } private bool col_to_bool_or_default(string key, bool def) { string? val = db.settings.select({db.settings.value}).with(db.settings.key, "=", key)[db.settings.value]; return val != null ? bool.parse(val) : def; } private bool send_typing_; public bool send_typing { get { return send_typing_; } set { db.settings.insert().or("REPLACE").value(db.settings.key, "send_typing").value(db.settings.value, value.to_string()).perform(); send_typing_ = value; } } private bool send_marker_; public bool send_marker { get { return send_marker_; } set { db.settings.insert().or("REPLACE").value(db.settings.key, "send_marker").value(db.settings.value, value.to_string()).perform(); send_marker_ = value; } } private bool notifications_; public bool notifications { get { return notifications_; } set { db.settings.insert().or("REPLACE").value(db.settings.key, "notifications").value(db.settings.value, value.to_string()).perform(); notifications_ = value; } } private bool convert_utf8_smileys_; public bool convert_utf8_smileys { get { return convert_utf8_smileys_; } set { db.settings.insert().or("REPLACE").value(db.settings.key, "convert_utf8_smileys").value(db.settings.value, value.to_string()).perform(); convert_utf8_smileys_ = value; } } } } dino-0.1.0/libdino/src/plugin/0000755000000000000000000000000013614354364014655 5ustar rootrootdino-0.1.0/libdino/src/plugin/interfaces.vala0000644000000000000000000001160313614354364017646 0ustar rootrootusing Dino.Entities; using Xmpp; namespace Dino.Plugins { public enum Priority { LOWEST, LOWER, DEFAULT, HIGHER, HIGHEST } public enum WidgetType { GTK } public interface RootInterface : Object { public abstract void registered(Dino.Application app); public abstract void shutdown(); } public interface EncryptionListEntry : Object { public abstract Entities.Encryption encryption { get; } public abstract string name { get; } public abstract void encryption_activated(Entities.Conversation conversation, Plugins.SetInputFieldStatus callback); } public abstract class AccountSettingsEntry : Object { public abstract string id { get; } public virtual Priority priority { get { return Priority.DEFAULT; } } public abstract string name { get; } public virtual int16 label_top_padding { get { return -1; } } public abstract AccountSettingsWidget? get_widget(WidgetType type); } public interface AccountSettingsWidget : Object { public abstract void set_account(Account account); public abstract signal void activated(); public abstract void deactivate(); } public interface ContactDetailsProvider : Object { public abstract string id { get; } public abstract void populate(Conversation conversation, ContactDetails contact_details, WidgetType type); } public class ContactDetails : Object { public signal void save(); public signal void add(string category, string label, string? desc, Object widget); } public interface TextCommand : Object { public abstract string cmd { get; } public abstract string? handle_command(string? text, Entities.Conversation? conversation); } public interface ConversationTitlebarEntry : Object { public abstract string id { get; } public abstract double order { get; } public abstract ConversationTitlebarWidget? get_widget(WidgetType type); } public interface ConversationTitlebarWidget : Object { public abstract void set_conversation(Conversation conversation); public abstract void unset_conversation(); } public abstract interface ConversationItemPopulator : Object { public abstract string id { get; } public abstract void init(Conversation conversation, ConversationItemCollection summary, WidgetType type); public abstract void close(Conversation conversation); } public abstract interface ConversationAdditionPopulator : ConversationItemPopulator { public virtual void populate_timespan(Conversation conversation, DateTime from, DateTime to) { } } public abstract interface NotificationPopulator : Object { public abstract string id { get; } public abstract void init(Conversation conversation, NotificationCollection summary, WidgetType type); public abstract void close(Conversation conversation); } public abstract class MetaConversationItem : Object { public virtual string populator_id { get; set; } public virtual Jid? jid { get; set; default=null; } public virtual bool dim { get; set; default=false; } public virtual DateTime sort_time { get; set; default = new DateTime.now_utc(); } public virtual long seccondary_sort_indicator { get; set; } public virtual long tertiary_sort_indicator { get; set; } public virtual DateTime? display_time { get; set; default = null; } public virtual Encryption encryption { get; set; default = Encryption.NONE; } public virtual Entities.Message.Marked mark { get; set; default = Entities.Message.Marked.NONE; } public abstract bool can_merge { get; set; } public abstract bool requires_avatar { get; set; } public abstract bool requires_header { get; set; } public abstract Object? get_widget(WidgetType type); } public abstract class MetaConversationNotification : Object { public abstract Object? get_widget(WidgetType type); } public interface ConversationItemCollection : Object { public signal void inserted_item(MetaConversationItem item); public signal void removed_item(MetaConversationItem item); public abstract void insert_item(MetaConversationItem item); public abstract void remove_item(MetaConversationItem item); } public interface NotificationCollection : Object { public signal void add_meta_notification(MetaConversationNotification item); public signal void remove_meta_notification(MetaConversationNotification item); } public delegate void SetInputFieldStatus(InputFieldStatus field_status); public class InputFieldStatus : Object { public enum MessageType { NONE, INFO, WARNING, ERROR } public enum InputState { NORMAL, DISABLED, NO_SEND } public string? message; public MessageType message_type; public InputState input_state; public InputFieldStatus(string? message, MessageType message_type, InputState input_state) { this.message = message; this.message_type = message_type; this.input_state = input_state; } } } dino-0.1.0/libdino/src/plugin/loader.vala0000644000000000000000000000532113614354364016771 0ustar rootrootusing Gee; namespace Dino.Plugins { private class Info : Object { public Module module; public Type gtype; public Info(Type type, owned Module module) { this.module = (owned) module; this.gtype = type; } } public class Loader : Object { [CCode (has_target = false)] private delegate Type RegisterPluginFunction(Module module); private Application app; private string[] search_paths; private RootInterface[] plugins = new RootInterface[0]; private Info[] infos = new Info[0]; public Loader(Application app) { this.app = app; this.search_paths = app.search_path_generator.get_plugin_paths(); } public void loadAll() throws Error { if (Module.supported() == false) { throw new Error(-1, 0, "Plugins are not supported"); } HashSet plugin_names = new HashSet(); foreach (string path in search_paths) { try { Dir dir = Dir.open(path, 0); string? file = null; while ((file = dir.read_name()) != null) { if (file.has_suffix(Module.SUFFIX)) plugin_names.add(file); } } catch (Error e) { // Ignore this folder } } foreach (string plugin in plugin_names) { load(plugin); } } public RootInterface load(string name) throws Error { if (Module.supported() == false) { throw new Error(-1, 0, "Plugins are not supported"); } Module module = null; string path = ""; foreach (string prefix in search_paths) { path = Path.build_filename(prefix, name); module = Module.open(path, ModuleFlags.BIND_LAZY); if (module != null) break; } if (module == null) { throw new Error(-1, 1, "%s", Module.error().replace(path, name)); } void* function; module.symbol("register_plugin", out function); if (function == null) { throw new Error(-1, 2, "register_plugin () not found"); } RegisterPluginFunction register_plugin = (RegisterPluginFunction) function; Type type = register_plugin(module); if (type.is_a(typeof(RootInterface)) == false) { throw new Error(-1, 3, "Unexpected type"); } Info info = new Plugins.Info(type, (owned) module); infos += info; RootInterface plugin = (RootInterface) Object.new (type); plugins += plugin; plugin.registered(app); return plugin; } public void shutdown() { foreach (RootInterface p in plugins) { p.shutdown(); } } } } dino-0.1.0/libdino/src/plugin/registry.vala0000644000000000000000000000710313614354364017373 0ustar rootrootusing Gee; namespace Dino.Plugins { public class Registry { internal ArrayList encryption_list_entries = new ArrayList(); internal ArrayList account_settings_entries = new ArrayList(); internal ArrayList contact_details_entries = new ArrayList(); internal Map text_commands = new HashMap(); internal Gee.List conversation_addition_populators = new ArrayList(); internal Gee.List notification_populators = new ArrayList(); internal Gee.Collection conversation_titlebar_entries = new Gee.TreeSet((a, b) => { if (a.order < b.order) { return 1; } else if (a.order > b.order) { return -1; } else { return 0; } }); public bool register_encryption_list_entry(EncryptionListEntry entry) { lock(encryption_list_entries) { foreach(var e in encryption_list_entries) { if (e.encryption == entry.encryption) return false; } encryption_list_entries.add(entry); encryption_list_entries.sort((a,b) => b.name.collate(a.name)); return true; } } public bool register_account_settings_entry(AccountSettingsEntry entry) { lock(account_settings_entries) { foreach(var e in account_settings_entries) { if (e.id == entry.id) return false; } account_settings_entries.add(entry); // TODO: Order by priority account_settings_entries.sort((a,b) => b.name.collate(a.name)); return true; } } public bool register_contact_details_entry(ContactDetailsProvider entry) { lock(contact_details_entries) { foreach(ContactDetailsProvider e in contact_details_entries) { if (e.id == entry.id) return false; } contact_details_entries.add(entry); return true; } } public bool register_text_command(TextCommand cmd) { lock(text_commands) { if (text_commands.has_key(cmd.cmd)) return false; text_commands[cmd.cmd] = cmd; return true; } } public bool register_contact_titlebar_entry(ConversationTitlebarEntry entry) { lock(conversation_titlebar_entries) { foreach(ConversationTitlebarEntry e in conversation_titlebar_entries) { if (e.id == entry.id) return false; } conversation_titlebar_entries.add(entry); return true; } } public bool register_conversation_addition_populator(ConversationAdditionPopulator populator) { lock (conversation_addition_populators) { foreach(ConversationItemPopulator p in conversation_addition_populators) { if (p.id == populator.id) return false; } conversation_addition_populators.add(populator); return true; } } public bool register_notification_populator(NotificationPopulator populator) { lock (notification_populators) { foreach(NotificationPopulator p in notification_populators) { if (p.id == populator.id) return false; } notification_populators.add(populator); return true; } } } } dino-0.1.0/libdino/src/service/0000755000000000000000000000000013614354364015017 5ustar rootrootdino-0.1.0/libdino/src/service/avatar_manager.vala0000644000000000000000000001600513614354364020636 0ustar rootrootusing Gdk; using Gee; using Xmpp; using Dino.Entities; namespace Dino { public class AvatarManager : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("avatar_manager"); public string id { get { return IDENTITY.id; } } public signal void received_avatar(Pixbuf avatar, Jid jid, Account account); private enum Source { USER_AVATARS, VCARD } private StreamInteractor stream_interactor; private Database db; private HashMap user_avatars = new HashMap(Jid.hash_func, Jid.equals_func); private HashMap vcard_avatars = new HashMap(Jid.hash_func, Jid.equals_func); private AvatarStorage avatar_storage = new AvatarStorage(get_storage_dir()); private HashMap cached_pixbuf = new HashMap(); private HashMap> pending_pixbuf = new HashMap>(); private const int MAX_PIXEL = 192; public static void start(StreamInteractor stream_interactor, Database db) { AvatarManager m = new AvatarManager(stream_interactor, db); stream_interactor.add_module(m); } public static string get_storage_dir() { return Path.build_filename(Dino.get_storage_dir(), "avatars"); } private AvatarManager(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; this.db = db; stream_interactor.account_added.connect(on_account_added); stream_interactor.module_manager.initialize_account_modules.connect(initialize_avatar_modules); } private void initialize_avatar_modules(Account account, ArrayList modules) { modules.add(new Xep.UserAvatars.Module(avatar_storage)); modules.add(new Xep.VCard.Module(avatar_storage)); } private async Pixbuf? get_avatar_by_hash(string hash) { if (cached_pixbuf.has_key(hash)) { return cached_pixbuf[hash]; } if (pending_pixbuf.has_key(hash)) { pending_pixbuf[hash].add(new SourceFuncWrapper(get_avatar_by_hash.callback)); yield; return cached_pixbuf[hash]; } pending_pixbuf[hash] = new ArrayList(); Pixbuf? image = yield avatar_storage.get_image(hash); if (image != null) { cached_pixbuf[hash] = image; } else { db.avatar.delete().with(db.avatar.hash, "=", hash).perform(); } foreach (SourceFuncWrapper sfw in pending_pixbuf[hash]) { sfw.sfun(); } return image; } public bool has_avatar(Account account, Jid jid) { string? hash = get_avatar_hash(account, jid); if (hash != null) { if (cached_pixbuf.has_key(hash)) { return true; } return avatar_storage.has_image(hash); } return false; } public async Pixbuf? get_avatar(Account account, Jid jid) { Jid jid_ = jid; if (!stream_interactor.get_module(MucManager.IDENTITY).is_groupchat_occupant(jid, account)) { jid_ = jid.bare_jid; } string? hash = get_avatar_hash(account, jid_); if (hash != null) { return yield get_avatar_by_hash(hash); } return null; } private string? get_avatar_hash(Account account, Jid jid) { string? user_avatars_id = user_avatars[jid]; if (user_avatars_id != null) { return user_avatars_id; } string? vcard_avatars_id = vcard_avatars[jid]; if (vcard_avatars_id != null) { return vcard_avatars_id; } return null; } public void publish(Account account, string file) { try { Pixbuf pixbuf = new Pixbuf.from_file(file); if (pixbuf.width >= pixbuf.height && pixbuf.width > MAX_PIXEL) { int dest_height = (int) ((float) MAX_PIXEL / pixbuf.width * pixbuf.height); pixbuf = pixbuf.scale_simple(MAX_PIXEL, dest_height, InterpType.BILINEAR); } else if (pixbuf.height > pixbuf.width && pixbuf.width > MAX_PIXEL) { int dest_width = (int) ((float) MAX_PIXEL / pixbuf.height * pixbuf.width); pixbuf = pixbuf.scale_simple(dest_width, MAX_PIXEL, InterpType.BILINEAR); } uint8[] buffer; pixbuf.save_to_buffer(out buffer, "png"); XmppStream stream = stream_interactor.get_stream(account); if (stream != null) { stream.get_module(Xep.UserAvatars.Module.IDENTITY).publish_png(stream, buffer, pixbuf.width, pixbuf.height); on_user_avatar_received(account, account.bare_jid, Base64.encode(buffer)); } } catch (Error e) { warning(e.message); } } private void on_account_added(Account account) { stream_interactor.module_manager.get_module(account, Xep.UserAvatars.Module.IDENTITY).received_avatar.connect((stream, jid, id) => on_user_avatar_received(account, jid, id) ); stream_interactor.module_manager.get_module(account, Xep.VCard.Module.IDENTITY).received_avatar.connect((stream, jid, id) => on_vcard_avatar_received(account, jid, id) ); foreach (var entry in db.get_avatar_hashes(Source.USER_AVATARS).entries) { on_user_avatar_received(account, entry.key, entry.value); } foreach (var entry in db.get_avatar_hashes(Source.VCARD).entries) { // FIXME: remove. temporary to remove falsely saved avatars. if (stream_interactor.get_module(MucManager.IDENTITY).is_groupchat(entry.key, account)) { db.avatar.delete().with(db.avatar.jid, "=", entry.key.to_string()).perform(); continue; } on_vcard_avatar_received(account, entry.key, entry.value); } } private void on_user_avatar_received(Account account, Jid jid, string id) { if (!user_avatars.has_key(jid) || user_avatars[jid] != id) { user_avatars[jid] = id; db.set_avatar_hash(jid, id, Source.USER_AVATARS); } avatar_storage.get_image.begin(id, (obj, res) => { Pixbuf? avatar = avatar_storage.get_image.end(res); if (avatar != null) { received_avatar(avatar, jid, account); } }); } private void on_vcard_avatar_received(Account account, Jid jid, string id) { if (!vcard_avatars.has_key(jid) || vcard_avatars[jid] != id) { vcard_avatars[jid] = id; if (!jid.is_full()) { // don't save MUC occupant avatars db.set_avatar_hash(jid, id, Source.VCARD); } } avatar_storage.get_image.begin(id, (obj, res) => { Pixbuf? avatar = avatar_storage.get_image.end(res); if (avatar != null) { received_avatar(avatar, jid, account); } }); } } } dino-0.1.0/libdino/src/service/avatar_storage.vala0000644000000000000000000000312513614354364020667 0ustar rootrootusing Gdk; using Xmpp; namespace Dino { public class AvatarStorage : Xep.PixbufStorage, Object { string folder; public AvatarStorage(string folder) { this.folder = folder; DirUtils.create_with_parents(folder, 0700); } public void store(string id, Bytes data) { File file = File.new_for_path(Path.build_filename(folder, id)); try { if (file.query_exists()) file.delete(); //TODO y? DataOutputStream fos = new DataOutputStream(file.create(FileCreateFlags.REPLACE_DESTINATION)); fos.write_bytes_async.begin(data); } catch (Error e) { // Ignore: we failed in storing, so we refuse to display later... } } public bool has_image(string id) { File file = File.new_for_path(Path.build_filename(folder, id)); return file.query_exists(); } public async Pixbuf? get_image(string id) { try { File file = File.new_for_path(Path.build_filename(folder, id)); FileInputStream stream = yield file.read_async(); uint8 fbuf[1024]; size_t size; Checksum checksum = new Checksum (ChecksumType.SHA1); while ((size = yield stream.read_async(fbuf)) > 0) { checksum.update(fbuf, size); } if (checksum.get_string() != id) { FileUtils.remove(file.get_path()); } stream.seek(0, SeekType.SET); return yield new Pixbuf.from_stream_async(stream, null); } catch (Error e) { return null; } } } } dino-0.1.0/libdino/src/service/blocking_manager.vala0000644000000000000000000000324013614354364021145 0ustar rootrootusing Gee; using Xmpp; using Dino.Entities; namespace Dino { public class BlockingManager : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("blocking_manager"); public string id { get { return IDENTITY.id; } } private StreamInteractor stream_interactor; public static void start(StreamInteractor stream_interactor) { BlockingManager m = new BlockingManager(stream_interactor); stream_interactor.add_module(m); } private BlockingManager(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public bool is_blocked(Account account, Jid jid) { XmppStream stream = stream_interactor.get_stream(account); return stream != null && stream.get_module(Xmpp.Xep.BlockingCommand.Module.IDENTITY).is_blocked(stream, jid.to_string()); } public void block(Account account, Jid jid) { XmppStream stream = stream_interactor.get_stream(account); stream.get_module(Xmpp.Xep.BlockingCommand.Module.IDENTITY).block(stream, new ArrayList.wrap(new string[] {jid.to_string()})); } public void unblock(Account account, Jid jid) { XmppStream stream = stream_interactor.get_stream(account); stream.get_module(Xmpp.Xep.BlockingCommand.Module.IDENTITY).unblock(stream, new ArrayList.wrap(new string[] {jid.to_string()})); } public bool is_supported(Account account) { XmppStream stream = stream_interactor.get_stream(account); return stream != null && stream.get_module(Xmpp.Xep.BlockingCommand.Module.IDENTITY).is_supported(stream); } } } dino-0.1.0/libdino/src/service/chat_interaction.vala0000644000000000000000000002576013614354364021214 0ustar rootrootusing Gee; using Xmpp; using Dino.Entities; namespace Dino { public class ChatInteraction : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("chat_interaction"); public string id { get { return IDENTITY.id; } } public signal void focused_in(Conversation conversation); public signal void focused_out(Conversation conversation); private StreamInteractor stream_interactor; private Conversation? selected_conversation; private HashMap last_input_interaction = new HashMap(Conversation.hash_func, Conversation.equals_func); private HashMap last_interface_interaction = new HashMap(Conversation.hash_func, Conversation.equals_func); private bool focus_in = false; public static void start(StreamInteractor stream_interactor) { ChatInteraction m = new ChatInteraction(stream_interactor); stream_interactor.add_module(m); } private ChatInteraction(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; Timeout.add_seconds(30, update_interactions); stream_interactor.get_module(MessageProcessor.IDENTITY).received_pipeline.connect(new ReceivedMessageListener(stream_interactor)); stream_interactor.get_module(MessageProcessor.IDENTITY).message_sent.connect(on_message_sent); } public bool has_unread(Conversation conversation) { ContentItem? last_content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_latest(conversation); if (last_content_item == null) return false; MessageItem? message_item = last_content_item as MessageItem; if (message_item != null) { Message last_message = message_item.message; // We are the message sender if (last_message.from.equals_bare(conversation.account.bare_jid)) return false; // We read up to the message if (conversation.read_up_to != null && last_message.equals(conversation.read_up_to)) return false; return true; } FileItem? file_item = last_content_item as FileItem; if (file_item != null) { FileTransfer file_transfer = file_item.file_transfer; // We are the file sender if (file_transfer.from.equals_bare(conversation.account.bare_jid)) return false; if (file_transfer.provider == 0) { // HTTP file transfer: Check if the associated message is the last one if (file_transfer.info == null) return false; Message? message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_id(int.parse(file_transfer.info), conversation); if (message == null) return false; if (message.equals(conversation.read_up_to)) return false; } if (file_transfer.provider == 1) { if (file_transfer.state == FileTransfer.State.COMPLETE) return false; } return true; } return false; } public bool is_active_focus(Conversation? conversation = null) { if (conversation != null) { return focus_in && conversation.equals(this.selected_conversation); } else { return focus_in; } } public void on_window_focus_in(Conversation? conversation) { on_conversation_focused(conversation); } public void on_window_focus_out(Conversation? conversation) { on_conversation_unfocused(conversation); } public void on_message_entered(Conversation? conversation) { if (!last_input_interaction.has_key(conversation)) { send_chat_state_notification(conversation, Xep.ChatStateNotifications.STATE_COMPOSING); } last_input_interaction[conversation] = new DateTime.now_utc(); last_interface_interaction[conversation] = new DateTime.now_utc(); } public void on_message_cleared(Conversation? conversation) { if (last_input_interaction.has_key(conversation)) { last_input_interaction.unset(conversation); send_chat_state_notification(conversation, Xep.ChatStateNotifications.STATE_ACTIVE); } } public void on_conversation_selected(Conversation conversation) { on_conversation_unfocused(selected_conversation); selected_conversation = conversation; on_conversation_focused(conversation); } private void on_message_sent(Entities.Message message, Conversation conversation) { last_input_interaction.unset(conversation); last_interface_interaction.unset(conversation); conversation.read_up_to = message; } private void on_conversation_focused(Conversation? conversation) { focus_in = true; if (conversation == null) return; focused_in(selected_conversation); check_send_read(); selected_conversation.read_up_to = stream_interactor.get_module(MessageStorage.IDENTITY).get_last_message(conversation); } private void on_conversation_unfocused(Conversation? conversation) { focus_in = false; if (conversation == null) return; focused_out(selected_conversation); if (last_input_interaction.has_key(conversation)) { send_chat_state_notification(conversation, Xep.ChatStateNotifications.STATE_PAUSED); last_input_interaction.unset(conversation); } } private void check_send_read() { if (selected_conversation == null || selected_conversation.type_ == Conversation.Type.GROUPCHAT) return; Entities.Message? message = stream_interactor.get_module(MessageStorage.IDENTITY).get_last_message(selected_conversation); if (message != null && message.direction == Entities.Message.DIRECTION_RECEIVED && !message.equals(selected_conversation.read_up_to)) { selected_conversation.read_up_to = message; send_chat_marker(message, null, selected_conversation, Xep.ChatMarkers.MARKER_DISPLAYED); } } private bool update_interactions() { for (MapIterator iter = last_input_interaction.map_iterator(); iter.has_next(); iter.next()) { if (!iter.valid && iter.has_next()) iter.next(); Conversation conversation = iter.get_key(); if (last_input_interaction.has_key(conversation) && (new DateTime.now_utc()).difference(last_input_interaction[conversation]) >= 15 * TimeSpan.SECOND) { iter.unset(); send_chat_state_notification(conversation, Xep.ChatStateNotifications.STATE_PAUSED); } } for (MapIterator iter = last_interface_interaction.map_iterator(); iter.has_next(); iter.next()) { if (!iter.valid && iter.has_next()) iter.next(); Conversation conversation = iter.get_key(); if (last_interface_interaction.has_key(conversation) && (new DateTime.now_utc()).difference(last_interface_interaction[conversation]) >= 1.5 * TimeSpan.MINUTE) { iter.unset(); send_chat_state_notification(conversation, Xep.ChatStateNotifications.STATE_GONE); } } return true; } private class ReceivedMessageListener : MessageListener { public string[] after_actions_const = new string[]{ "DEDUPLICATE", "FILTER_EMPTY" }; public override string action_group { get { return "OTHER_NODES"; } } public override string[] after_actions { get { return after_actions_const; } } private StreamInteractor stream_interactor; public ReceivedMessageListener(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) { if (Xep.MessageArchiveManagement.MessageFlag.get_flag(stanza) != null) return false; ChatInteraction outer = stream_interactor.get_module(ChatInteraction.IDENTITY); outer.send_delivery_receipt(message, stanza, conversation); // Send chat marker if (message.direction == Entities.Message.DIRECTION_SENT) return false; if (outer.is_active_focus(conversation)) { outer.check_send_read(); conversation.read_up_to = message; outer.send_chat_marker(message, stanza, conversation, Xep.ChatMarkers.MARKER_DISPLAYED); } else { outer.send_chat_marker(message, stanza, conversation, Xep.ChatMarkers.MARKER_RECEIVED); } return false; } } private void send_chat_marker(Entities.Message message, Xmpp.MessageStanza? stanza, Conversation conversation, string marker) { XmppStream? stream = stream_interactor.get_stream(conversation.account); if (stream == null) return; if (message.stanza_id == null) return; // Need a stanza id to mark switch (marker) { case Xep.ChatMarkers.MARKER_RECEIVED: if (stanza != null && Xep.ChatMarkers.Module.requests_marking(stanza)) { stream.get_module(Xep.ChatMarkers.Module.IDENTITY).send_marker(stream, message.from, message.stanza_id, message.get_type_string(), Xep.ChatMarkers.MARKER_RECEIVED); } break; case Xep.ChatMarkers.MARKER_DISPLAYED: if (conversation.get_send_marker_setting(stream_interactor) == Conversation.Setting.ON) { stream.get_module(Xep.ChatMarkers.Module.IDENTITY).send_marker(stream, message.from, message.stanza_id, message.get_type_string(), Xep.ChatMarkers.MARKER_DISPLAYED); } break; } } private void send_delivery_receipt(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) { if (message.direction == Entities.Message.DIRECTION_SENT) return; if (!Xep.MessageDeliveryReceipts.Module.requests_receipt(stanza)) return; if (conversation.type_ == Conversation.Type.GROUPCHAT) return; XmppStream? stream = stream_interactor.get_stream(conversation.account); if (stream != null) { stream.get_module(Xep.MessageDeliveryReceipts.Module.IDENTITY).send_received(stream, message.from, message.stanza_id); } } private void send_chat_state_notification(Conversation conversation, string state) { if (conversation.get_send_typing_setting(stream_interactor) != Conversation.Setting.ON) return; XmppStream? stream = stream_interactor.get_stream(conversation.account); if (stream != null) { string message_type = conversation.type_ == Conversation.Type.GROUPCHAT ? Xmpp.MessageStanza.TYPE_GROUPCHAT : Xmpp.MessageStanza.TYPE_CHAT; stream.get_module(Xep.ChatStateNotifications.Module.IDENTITY).send_state(stream, conversation.counterpart, message_type, state); } } } } dino-0.1.0/libdino/src/service/connection_manager.vala0000644000000000000000000002650213614354364021522 0ustar rootrootusing Gee; using Xmpp; using Dino.Entities; namespace Dino { public class ConnectionManager : Object { public signal void stream_opened(Account account, XmppStream stream); public signal void connection_state_changed(Account account, ConnectionState state); public signal void connection_error(Account account, ConnectionError error); public enum ConnectionState { CONNECTED, CONNECTING, DISCONNECTED } private HashSet connection_todo = new HashSet(Account.hash_func, Account.equals_func); private HashMap connections = new HashMap(Account.hash_func, Account.equals_func); private HashMap connection_errors = new HashMap(Account.hash_func, Account.equals_func); private NetworkMonitor? network_monitor; private Login1Manager? login1; private ModuleManager module_manager; public string? log_options; public class ConnectionError { public enum Source { CONNECTION, SASL, TLS, STREAM_ERROR } public enum Reconnect { NOW, LATER, NEVER } public Source source; public string? identifier; public Reconnect reconnect_recomendation { get; set; default=Reconnect.NOW; } public ConnectionError(Source source, string? identifier) { this.source = source; this.identifier = identifier; } } private class Connection { public XmppStream stream { get; set; } public ConnectionState connection_state { get; set; default = ConnectionState.DISCONNECTED; } public DateTime established { get; set; } public DateTime last_activity { get; set; } public class Connection(XmppStream stream, DateTime established) { this.stream = stream; this.established = established; } } public ConnectionManager(ModuleManager module_manager) { this.module_manager = module_manager; network_monitor = GLib.NetworkMonitor.get_default(); if (network_monitor != null) { network_monitor.network_changed.connect(on_network_changed); network_monitor.notify["connectivity"].connect(on_network_changed); } login1 = get_login1(); if (login1 != null) { login1.PrepareForSleep.connect(on_prepare_for_sleep); } Timeout.add_seconds(60, () => { foreach (Account account in connection_todo) { if (connections[account].last_activity != null && connections[account].last_activity.compare(new DateTime.now_utc().add_minutes(-1)) < 0) { check_reconnect(account); } } return true; }); } public XmppStream? get_stream(Account account) { if (get_state(account) == ConnectionState.CONNECTED) { return connections[account].stream; } return null; } public ConnectionState get_state(Account account) { if (connections.has_key(account)){ return connections[account].connection_state; } return ConnectionState.DISCONNECTED; } public ConnectionError? get_error(Account account) { if (connection_errors.has_key(account)) { return connection_errors[account]; } return null; } public Collection get_managed_accounts() { return connection_todo; } public void connect_account(Account account) { if (!connection_todo.contains(account)) connection_todo.add(account); if (!connections.has_key(account)) { connect_(account); } else { check_reconnect(account); } } public void make_offline_all() { foreach (Account account in connections.keys) { make_offline(account); } } private void make_offline(Account account) { Xmpp.Presence.Stanza presence = new Xmpp.Presence.Stanza(); presence.type_ = Xmpp.Presence.Stanza.TYPE_UNAVAILABLE; change_connection_state(account, ConnectionState.DISCONNECTED); connections[account].stream.get_module(Presence.Module.IDENTITY).send_presence(connections[account].stream, presence); } public async void disconnect_account(Account account) { if (connections.has_key(account)) { make_offline(account); try { yield connections[account].stream.disconnect(); } catch (Error e) { debug("Error disconnecting stream: %s", e.message); } connection_todo.remove(account); if (connections.has_key(account)) { connections.unset(account); } } } private void connect_(Account account, string? resource = null) { if (connections.has_key(account)) connections[account].stream.detach_modules(); connection_errors.unset(account); if (resource == null) resource = account.resourcepart; XmppStream stream = new XmppStream(); foreach (XmppStreamModule module in module_manager.get_modules(account, resource)) { stream.add_module(module); } stream.log = new XmppLog(account.bare_jid.to_string(), log_options); debug("[%s] New connection with resource %s: %p", account.bare_jid.to_string(), resource, stream); Connection connection = new Connection(stream, new DateTime.now_utc()); connections[account] = connection; change_connection_state(account, ConnectionState.CONNECTING); stream.attached_modules.connect((stream) => { change_connection_state(account, ConnectionState.CONNECTED); }); stream.get_module(Sasl.Module.IDENTITY).received_auth_failure.connect((stream, node) => { set_connection_error(account, new ConnectionError(ConnectionError.Source.SASL, null)); }); stream.get_module(Tls.Module.IDENTITY).invalid_certificate.connect(() => { set_connection_error(account, new ConnectionError(ConnectionError.Source.TLS, null) { reconnect_recomendation=ConnectionError.Reconnect.NEVER}); }); stream.received_node.connect(() => { connection.last_activity = new DateTime.now_utc(); }); connect_async.begin(account, stream); stream_opened(account, stream); } private async void connect_async(Account account, XmppStream stream) { try { yield stream.connect(account.domainpart); } catch (Error e) { debug("[%s %p] Error: %s", account.bare_jid.to_string(), stream, e.message); change_connection_state(account, ConnectionState.DISCONNECTED); if (!connection_todo.contains(account)) { return; } StreamError.Flag? flag = stream.get_flag(StreamError.Flag.IDENTITY); if (flag != null) { warning(@"[%s %p] Stream Error: %s", account.bare_jid.to_string(), stream, flag.error_type); set_connection_error(account, new ConnectionError(ConnectionError.Source.STREAM_ERROR, flag.error_type)); if (flag.resource_rejected) { connect_(account, account.resourcepart + "-" + random_uuid()); return; } } ConnectionError? error = connection_errors[account]; if (error != null && error.source == ConnectionError.Source.SASL) { return; } debug("[%s] Check reconnect in 5 sec", account.bare_jid.to_string()); Timeout.add_seconds(5, () => { check_reconnect(account); return false; }); } } private void check_reconnects() { foreach (Account account in connection_todo) { check_reconnect(account); } } private void check_reconnect(Account account) { if (!connections.has_key(account)) return; bool acked = false; DateTime? last_activity_was = connections[account].last_activity; XmppStream stream = connections[account].stream; stream.get_module(Xep.Ping.Module.IDENTITY).send_ping(stream, account.bare_jid.domain_jid, () => { acked = true; if (connections[account].stream != stream) return; change_connection_state(account, ConnectionState.CONNECTED); }); Timeout.add_seconds(10, () => { if (!connections.has_key(account)) return false; if (connections[account].stream != stream) return false; if (acked) return false; if (connections[account].last_activity != last_activity_was) return false; // Reconnect. Nothing gets through the stream. debug("[%s %p] Ping timeouted. Reconnecting", account.bare_jid.to_string(), stream); change_connection_state(account, ConnectionState.DISCONNECTED); connections[account].stream.disconnect.begin((_, res) => { try { connections[account].stream.disconnect.end(res); } catch (Error e) { debug("Error disconnecting stream: %s", e.message); } }); connect_(account); return false; }); } private bool network_is_online() { /* FIXME: We should also check for connectivity eventually. For more * details on why we don't do it for now, see: * * - https://github.com/dino/dino/pull/236#pullrequestreview-86851793 * - https://bugzilla.gnome.org/show_bug.cgi?id=792240 */ return network_monitor != null && network_monitor.network_available; } private void on_network_changed() { if (network_is_online()) { debug("NetworkMonitor: Network reported online"); check_reconnects(); } else { debug("NetworkMonitor: Network reported offline"); foreach (Account account in connection_todo) { change_connection_state(account, ConnectionState.DISCONNECTED); } } } private async void on_prepare_for_sleep(bool suspend) { foreach (Account account in connection_todo) { change_connection_state(account, ConnectionState.DISCONNECTED); } if (suspend) { debug("Login1: Device suspended"); foreach (Account account in connection_todo) { try { make_offline(account); yield connections[account].stream.disconnect(); } catch (Error e) { debug("Error disconnecting stream %p: %s", connections[account].stream, e.message); } } } else { debug("Login1: Device un-suspend"); check_reconnects(); } } private void change_connection_state(Account account, ConnectionState state) { if (connections.has_key(account)) { connections[account].connection_state = state; connection_state_changed(account, state); } } private void set_connection_error(Account account, ConnectionError error) { connection_errors[account] = error; connection_error(account, error); } } } dino-0.1.0/libdino/src/service/content_item_store.vala0000644000000000000000000003313213614354364021572 0ustar rootrootusing Gee; using Dino.Entities; using Qlite; using Xmpp; namespace Dino { public class ContentItemStore : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("content_item_store"); public string id { get { return IDENTITY.id; } } public signal void new_item(ContentItem item, Conversation conversation); private StreamInteractor stream_interactor; private Database db; private Gee.List filters = new ArrayList(); private HashMap collection_conversations = new HashMap(Conversation.hash_func, Conversation.equals_func); public static void start(StreamInteractor stream_interactor, Database db) { ContentItemStore m = new ContentItemStore(stream_interactor, db); stream_interactor.add_module(m); } public ContentItemStore(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; this.db = db; stream_interactor.get_module(FileManager.IDENTITY).received_file.connect(insert_file_transfer); stream_interactor.get_module(MessageProcessor.IDENTITY).message_received.connect(announce_message); stream_interactor.get_module(MessageProcessor.IDENTITY).message_sent.connect(announce_message); } public void init(Conversation conversation, ContentItemCollection item_collection) { collection_conversations[conversation] = item_collection; } public void uninit(Conversation conversation, ContentItemCollection item_collection) { collection_conversations.unset(conversation); } public Gee.List get_items_from_query(QueryBuilder select, Conversation conversation) { Gee.TreeSet items = new Gee.TreeSet(ContentItem.compare); foreach (var row in select) { int provider = row[db.content_item.content_type]; int foreign_id = row[db.content_item.foreign_id]; switch (provider) { case 1: RowOption row_option = db.message.select().with(db.message.id, "=", foreign_id).row(); if (row_option.is_present()) { Message? message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_id(foreign_id, conversation); if (message == null) { try { message = new Message.from_row(db, row_option.inner); } catch (InvalidJidError e) { warning("Ignoring message with invalid Jid: %s", e.message); } } if (message != null) { items.add(new MessageItem(message, conversation, row[db.content_item.id])); } } break; case 2: RowOption row_option = db.file_transfer.select().with(db.file_transfer.id, "=", foreign_id).row(); if (row_option.is_present()) { try { string storage_dir = FileManager.get_storage_dir(); FileTransfer file_transfer = new FileTransfer.from_row(db, row_option.inner, storage_dir); if (conversation.type_.is_muc_semantic()) { try { // resourcepart wasn't set before, so we pick nickname instead (which isn't accurate if nickname is changed) file_transfer.ourpart = conversation.counterpart.with_resource(file_transfer.ourpart.resourcepart ?? conversation.nickname); } catch (InvalidJidError e) { warning("Failed setting file transfer Jid: %s", e.message); } } Message? message = null; if (file_transfer.provider == 0 && file_transfer.info != null) { message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_id(int.parse(file_transfer.info), conversation); } items.add(new FileItem(file_transfer, conversation, row[db.content_item.id], message)); } catch (InvalidJidError e) { warning("Ignoring file transfer with invalid Jid: %s", e.message); } } break; } } Gee.List ret = new ArrayList(); foreach (ContentItem item in items) { ret.add(item); } return ret; } public ContentItem? get_item(Conversation conversation, int type, int foreign_id) { QueryBuilder select = db.content_item.select() .with(db.content_item.content_type, "=", type) .with(db.content_item.foreign_id, "=", foreign_id); Gee.List item = get_items_from_query(select, conversation); return item.size > 0 ? item[0] : null; } public ContentItem? get_latest(Conversation conversation) { Gee.List items = get_n_latest(conversation, 1); if (items.size > 0) { return items.get(0); } return null; } public Gee.List get_n_latest(Conversation conversation, int count) { QueryBuilder select = db.content_item.select() .with(db.content_item.conversation_id, "=", conversation.id) .with(db.content_item.hide, "=", false) .order_by(db.content_item.local_time, "DESC") .order_by(db.content_item.time, "DESC") .limit(count); return get_items_from_query(select, conversation); } public Gee.List get_before(Conversation conversation, ContentItem item, int count) { long local_time = (long) item.sort_time.to_unix(); long time = (long) item.display_time.to_unix(); QueryBuilder select = db.content_item.select() .where(@"local_time < ? OR (local_time = ? AND time < ?) OR (local_time = ? AND time = ? AND id < ?)", { local_time.to_string(), local_time.to_string(), time.to_string(), local_time.to_string(), time.to_string(), item.id.to_string() }) .with(db.content_item.conversation_id, "=", conversation.id) .with(db.content_item.hide, "=", false) .order_by(db.content_item.local_time, "DESC") .order_by(db.content_item.time, "DESC") .limit(count); return get_items_from_query(select, conversation); } public Gee.List get_after(Conversation conversation, ContentItem item, int count) { long local_time = (long) item.sort_time.to_unix(); long time = (long) item.display_time.to_unix(); QueryBuilder select = db.content_item.select() .where(@"local_time > ? OR (local_time = ? AND time > ?) OR (local_time = ? AND time = ? AND id > ?)", { local_time.to_string(), local_time.to_string(), time.to_string(), local_time.to_string(), time.to_string(), item.id.to_string() }) .with(db.content_item.conversation_id, "=", conversation.id) .with(db.content_item.hide, "=", false) .order_by(db.content_item.local_time, "ASC") .order_by(db.content_item.time, "ASC") .limit(count); return get_items_from_query(select, conversation); } public void add_filter(ContentFilter content_filter) { filters.add(content_filter); } public void insert_message(Message message, Conversation conversation, bool hide = false) { MessageItem item = new MessageItem(message, conversation, -1); item.id = db.add_content_item(conversation, message.time, message.local_time, 1, message.id, hide); } private void announce_message(Message message, Conversation conversation) { QueryBuilder select = db.content_item.select(); select.with(db.content_item.foreign_id, "=", message.id); select.with(db.content_item.content_type, "=", 1); select.with(db.content_item.hide, "=", false); foreach (Row row in select) { MessageItem item = new MessageItem(message, conversation, row[db.content_item.id]); if (!discard(item)) { if (collection_conversations.has_key(conversation)) { collection_conversations.get(conversation).insert_item(item); } new_item(item, conversation); } break; } } private void insert_file_transfer(FileTransfer file_transfer, Conversation conversation) { FileItem item = new FileItem(file_transfer, conversation, -1); item.id = db.add_content_item(conversation, file_transfer.time, file_transfer.local_time, 2, file_transfer.id, false); if (!discard(item)) { if (collection_conversations.has_key(conversation)) { collection_conversations.get(conversation).insert_item(item); } new_item(item, conversation); } } public bool get_item_hide(ContentItem content_item) { return db.content_item.row_with(db.content_item.id, content_item.id)[db.content_item.hide, false]; } public void set_item_hide(ContentItem content_item, bool hide) { db.content_item.update() .with(db.content_item.id, "=", content_item.id) .set(db.content_item.hide, hide) .perform(); } private bool discard(ContentItem content_item) { foreach (ContentFilter filter in filters) { if (filter.discard(content_item)) { return true; } } return false; } } public interface ContentItemCollection : Object { public abstract void insert_item(ContentItem item); public abstract void remove_item(ContentItem item); } public interface ContentFilter : Object { public abstract bool discard(ContentItem content_item); } public abstract class ContentItem : Object { public int id { get; set; } public string type_ { get; set; } public Jid jid { get; set; } public DateTime sort_time { get; set; } public DateTime display_time { get; set; } public Encryption encryption { get; set; } public Entities.Message.Marked mark { get; set; } ContentItem(int id, string ty, Jid jid, DateTime sort_time, DateTime display_time, Encryption encryption, Entities.Message.Marked mark) { this.id = id; this.type_ = ty; this.jid = jid; this.sort_time = sort_time; this.display_time = display_time; this.encryption = encryption; this.mark = mark; } public static int compare(ContentItem a, ContentItem b) { int res = a.sort_time.compare(b.sort_time); if (res == 0) { res = a.display_time.compare(b.display_time); } if (res == 0) { res = a.id - b.id > 0 ? 1 : -1; } return res; } } public class MessageItem : ContentItem { public const string TYPE = "message"; public Message message; public Conversation conversation; public MessageItem(Message message, Conversation conversation, int id) { base(id, TYPE, message.from, message.local_time, message.time, message.encryption, message.marked); this.message = message; this.conversation = conversation; WeakRef weak_message = WeakRef(message); message.notify["marked"].connect(() => { Message? m = weak_message.get() as Message; if (m == null) return; mark = m.marked; }); } } public class FileItem : ContentItem { public const string TYPE = "file"; public FileTransfer file_transfer; public Conversation conversation; public FileItem(FileTransfer file_transfer, Conversation conversation, int id, Message? message = null) { Entities.Message.Marked mark = Entities.Message.Marked.NONE; if (message != null) { mark = message.marked; } else if (file_transfer.direction == FileTransfer.DIRECTION_SENT) { mark = file_to_message_state(file_transfer.state); } base(id, TYPE, file_transfer.from, file_transfer.local_time, file_transfer.time, file_transfer.encryption, mark); this.file_transfer = file_transfer; this.conversation = conversation; if (message != null) { WeakRef weak_message = WeakRef(message); message.notify["marked"].connect(() => { Message? m = weak_message.get() as Message; if (m == null) return; this.mark = m.marked; }); } else if (file_transfer.direction == FileTransfer.DIRECTION_SENT) { file_transfer.notify["state"].connect_after(() => { this.mark = file_to_message_state(file_transfer.state); }); } } private static Entities.Message.Marked file_to_message_state(FileTransfer.State state) { switch (state) { case FileTransfer.State.IN_PROGRESS: return Entities.Message.Marked.UNSENT; case FileTransfer.State.COMPLETE: return Entities.Message.Marked.NONE; case FileTransfer.State.NOT_STARTED: return Entities.Message.Marked.UNSENT; case FileTransfer.State.FAILED: return Entities.Message.Marked.WONTSEND; } assert_not_reached(); } } } dino-0.1.0/libdino/src/service/conversation_manager.vala0000644000000000000000000002020513614354364022067 0ustar rootrootusing Gee; using Xmpp; using Dino.Entities; namespace Dino { public class ConversationManager : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("conversation_manager"); public string id { get { return IDENTITY.id; } } public signal void conversation_activated(Conversation conversation); public signal void conversation_deactivated(Conversation conversation); private StreamInteractor stream_interactor; private Database db; private HashMap>> conversations = new HashMap>>(Account.hash_func, Account.equals_func); public static void start(StreamInteractor stream_interactor, Database db) { ConversationManager m = new ConversationManager(stream_interactor, db); stream_interactor.add_module(m); } private ConversationManager(StreamInteractor stream_interactor, Database db) { this.db = db; this.stream_interactor = stream_interactor; stream_interactor.add_module(this); stream_interactor.account_added.connect(on_account_added); stream_interactor.account_removed.connect(on_account_removed); stream_interactor.get_module(MessageProcessor.IDENTITY).received_pipeline.connect(new MessageListener(stream_interactor)); stream_interactor.get_module(MessageProcessor.IDENTITY).message_sent.connect(handle_sent_message); } public Conversation create_conversation(Jid jid, Account account, Conversation.Type? type = null) { assert(conversations.has_key(account)); Jid store_jid = type == Conversation.Type.GROUPCHAT ? jid.bare_jid : jid; // Do we already have a conversation for this jid? if (conversations[account].has_key(store_jid)) { foreach (var conversation in conversations[account][store_jid]) { if (conversation.type_ == type) { return conversation; } } } // Create a new converation Conversation conversation = new Conversation(jid, account, type); add_conversation(conversation); conversation.persist(db); return conversation; } public Conversation? get_conversation_for_message(Entities.Message message) { if (message.type_ == Entities.Message.Type.CHAT) { return create_conversation(message.counterpart.bare_jid, message.account, Conversation.Type.CHAT); } else if (message.type_ == Entities.Message.Type.GROUPCHAT) { return create_conversation(message.counterpart.bare_jid, message.account, Conversation.Type.GROUPCHAT); } else if (message.type_ == Entities.Message.Type.GROUPCHAT_PM) { return create_conversation(message.counterpart, message.account, Conversation.Type.GROUPCHAT_PM); } return null; } public Gee.List get_conversations_for_presence(Show show, Account account) { return get_conversations(show.jid, account); } public Gee.List get_conversations(Jid jid, Account account) { Gee.List ret = new ArrayList(Conversation.equals_func); Conversation? bare_conversation = get_conversation(jid, account); if (bare_conversation != null) ret.add(bare_conversation); Conversation? full_conversation = get_conversation(jid.bare_jid, account); if (full_conversation != null) ret.add(full_conversation); return ret; } public Conversation? get_conversation(Jid jid, Account account, Conversation.Type? type = null) { if (conversations.has_key(account)) { if (conversations[account].has_key(jid)) { foreach (var conversation in conversations[account][jid]) { if (type == null || conversation.type_ == type) { return conversation; } } } } return null; } public Conversation? get_conversation_by_id(int id) { foreach (HashMap> hm in conversations.values) { foreach (Gee.List hm2 in hm.values) { foreach (Conversation conversation in hm2) { if (conversation.id == id) { return conversation; } } } } return null; } public Gee.List get_active_conversations(Account? account = null) { Gee.List ret = new ArrayList(Conversation.equals_func); foreach (Account account_ in conversations.keys) { if (account != null && !account_.equals(account)) continue; foreach (Gee.List list in conversations[account_].values) { foreach (var conversation in list) { if(conversation.active) ret.add(conversation); } } } return ret; } public void start_conversation(Conversation conversation) { if (conversation.last_active == null) { conversation.last_active = new DateTime.now_utc(); if (conversation.active) conversation_activated(conversation); } if (!conversation.active) { conversation.active = true; conversation_activated(conversation); } } public void close_conversation(Conversation conversation) { if (!conversation.active) return; conversation.active = false; conversation_deactivated(conversation); } private void on_account_added(Account account) { conversations[account] = new HashMap>(Jid.hash_func, Jid.equals_func); foreach (Conversation conversation in db.get_conversations(account)) { add_conversation(conversation); } } private void on_account_removed(Account account) { foreach (Gee.List list in conversations[account].values) { foreach (var conversation in list) { if(conversation.active) conversation_deactivated(conversation); } } conversations.unset(account); } private class MessageListener : Dino.MessageListener { public string[] after_actions_const = new string[]{ "DEDUPLICATE", "FILTER_EMPTY" }; public override string action_group { get { return "MANAGER"; } } public override string[] after_actions { get { return after_actions_const; } } private StreamInteractor stream_interactor; public MessageListener(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) { conversation.last_active = message.time; if (stanza != null) { bool is_mam_message = Xep.MessageArchiveManagement.MessageFlag.get_flag(stanza) != null; bool is_recent = message.local_time.compare(new DateTime.now_utc().add_days(-3)) > 0; if (is_mam_message && !is_recent) return false; } stream_interactor.get_module(ConversationManager.IDENTITY).start_conversation(conversation); return false; } } private void handle_sent_message(Entities.Message message, Conversation conversation) { conversation.last_active = message.time; bool is_recent = message.local_time.compare(new DateTime.now_utc().add_hours(-24)) > 0; if (is_recent) { start_conversation(conversation); } } private void add_conversation(Conversation conversation) { if (!conversations[conversation.account].has_key(conversation.counterpart)) { conversations[conversation.account][conversation.counterpart] = new ArrayList(Conversation.equals_func); } conversations[conversation.account][conversation.counterpart].add(conversation); if (conversation.active) { conversation_activated(conversation); } } } } dino-0.1.0/libdino/src/service/counterpart_interaction_manager.vala0000644000000000000000000002216513614354364024331 0ustar rootrootusing Gee; using Xmpp; using Dino.Entities; namespace Dino { public class CounterpartInteractionManager : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("counterpart_interaction_manager"); public string id { get { return IDENTITY.id; } } public signal void received_state(Account account, Jid jid, string state); public signal void received_marker(Account account, Jid jid, Entities.Message message, Entities.Message.Marked marker); public signal void received_message_received(Account account, Jid jid, Entities.Message message); public signal void received_message_displayed(Account account, Jid jid, Entities.Message message); private StreamInteractor stream_interactor; private HashMap> chat_states = new HashMap>(Conversation.hash_func, Conversation.equals_func); private HashMap marker_wo_message = new HashMap(); public static void start(StreamInteractor stream_interactor) { CounterpartInteractionManager m = new CounterpartInteractionManager(stream_interactor); stream_interactor.add_module(m); } private CounterpartInteractionManager(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; stream_interactor.account_added.connect(on_account_added); stream_interactor.get_module(MessageProcessor.IDENTITY).received_pipeline.connect(new ReceivedMessageListener(this)); stream_interactor.get_module(MessageProcessor.IDENTITY).message_sent.connect(check_if_got_marker); stream_interactor.stream_negotiated.connect(() => chat_states.clear() ); } public HashMap? get_chat_states(Conversation conversation) { if (stream_interactor.connection_manager.get_state(conversation.account) != ConnectionManager.ConnectionState.CONNECTED) return null; return chat_states[conversation]; } private void on_account_added(Account account) { stream_interactor.module_manager.get_module(account, Xep.ChatMarkers.Module.IDENTITY).marker_received.connect( (stream, jid, marker, id) => { on_chat_marker_received(account, jid, marker, id); }); stream_interactor.module_manager.get_module(account, Xep.MessageDeliveryReceipts.Module.IDENTITY).receipt_received.connect((stream, jid, id) => { on_receipt_received(account, jid, id); }); stream_interactor.module_manager.get_module(account, Xep.ChatStateNotifications.Module.IDENTITY).chat_state_received.connect((stream, jid, state, stanza) => { on_chat_state_received.begin(account, jid, state, stanza); }); } private async void on_chat_state_received(Account account, Jid jid, string state, MessageStanza stanza) { // Don't show our own (other devices) typing notification if (jid.equals_bare(account.bare_jid)) return; Message message = yield stream_interactor.get_module(MessageProcessor.IDENTITY).parse_message_stanza(account, stanza); Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_for_message(message); if (conversation == null) return; // Don't show our own typing notification in MUCs if (conversation.type_ == Conversation.Type.GROUPCHAT) { Jid? own_muc_jid = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(jid.bare_jid, account); if (own_muc_jid != null && own_muc_jid.equals(jid)) { return; } } if (!chat_states.has_key(conversation)) { chat_states[conversation] = new HashMap(Jid.hash_func, Jid.equals_func); } if (state == Xmpp.Xep.ChatStateNotifications.STATE_ACTIVE) { chat_states[conversation].unset(jid); } else { chat_states[conversation][jid] = state; } received_state(account, jid, state); } private void on_chat_marker_received(Account account, Jid jid, string marker, string stanza_id) { // Check if the marker comes from ourselves (own jid or our jid in a MUC) bool own_marker = account.bare_jid.to_string() == jid.bare_jid.to_string(); if (stream_interactor.get_module(MucManager.IDENTITY).is_groupchat_occupant(jid, account)) { Jid? own_muc_jid = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(jid.bare_jid, account); if (own_muc_jid != null && own_muc_jid.equals(jid)) { own_marker = true; } } if (own_marker) { // If we received a display marker from ourselves (other device), set the conversation read up to that message. if (marker != Xep.ChatMarkers.MARKER_DISPLAYED && marker != Xep.ChatMarkers.MARKER_ACKNOWLEDGED) return; Conversation? conversation = stream_interactor.get_module(MessageStorage.IDENTITY).get_conversation_for_stanza_id(account, stanza_id); if (conversation == null || conversation.type_ == Conversation.Type.GROUPCHAT) return; Entities.Message? message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_stanza_id(stanza_id, conversation); if (message == null) return; // Don't move read marker backwards because we get old info from another client if (conversation.read_up_to == null || conversation.read_up_to.local_time.compare(message.local_time) > 0) return; conversation.read_up_to = message; } else { // We received a marker from someone else. Search the respective message and mark it. foreach (Conversation conversation in stream_interactor.get_module(ConversationManager.IDENTITY).get_conversations(jid, account)) { // We can't currently handle chat markers in MUCs if (conversation.type_ == Conversation.Type.GROUPCHAT) continue; Entities.Message? message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_stanza_id(stanza_id, conversation); if (message != null) { switch (marker) { case Xep.ChatMarkers.MARKER_RECEIVED: // If we got a received marker, mark the respective message received. received_message_received(account, jid, message); message.marked = Entities.Message.Marked.RECEIVED; break; case Xep.ChatMarkers.MARKER_DISPLAYED: // If we got a display marker, set all messages up to that message as read (if we know they've been received). received_message_displayed(account, jid, message); Gee.List messages = stream_interactor.get_module(MessageStorage.IDENTITY).get_messages(conversation); foreach (Entities.Message m in messages) { if (m.equals(message)) break; if (m.marked == Entities.Message.Marked.RECEIVED) m.marked = Entities.Message.Marked.READ; } message.marked = Entities.Message.Marked.READ; break; } } else { // We might get a marker before the actual message (on catchup). Save the marker. if (marker_wo_message.has_key(stanza_id) && marker_wo_message[stanza_id] == Xep.ChatMarkers.MARKER_DISPLAYED && marker == Xep.ChatMarkers.MARKER_RECEIVED) { return; } marker_wo_message[stanza_id] = marker; } } } } private void check_if_got_marker(Entities.Message message, Conversation conversation) { if (marker_wo_message.has_key(message.stanza_id)) { on_chat_marker_received(conversation.account, conversation.counterpart, marker_wo_message[message.stanza_id], message.stanza_id); marker_wo_message.unset(message.stanza_id); } } private class ReceivedMessageListener : MessageListener { public string[] after_actions_const = new string[]{ "DEDUPLICATE" }; public override string action_group { get { return "STORE"; } } public override string[] after_actions { get { return after_actions_const; } } private CounterpartInteractionManager outer; public ReceivedMessageListener(CounterpartInteractionManager outer) { this.outer = outer; } public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) { outer.on_chat_state_received.begin(conversation.account, conversation.counterpart, Xep.ChatStateNotifications.STATE_ACTIVE, stanza); return false; } } private void on_receipt_received(Account account, Jid jid, string id) { on_chat_marker_received(account, jid, Xep.ChatMarkers.MARKER_RECEIVED, id); } } } dino-0.1.0/libdino/src/service/database.vala0000644000000000000000000005601713614354364017441 0ustar rootrootusing Gee; using Qlite; using Xmpp; using Dino.Entities; namespace Dino { public class Database : Qlite.Database { private const int VERSION = 11; public class AccountTable : Table { public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; public Column bare_jid = new Column.Text("bare_jid") { unique = true, not_null = true }; public Column resourcepart = new Column.Text("resourcepart"); public Column password = new Column.Text("password"); public Column alias = new Column.Text("alias"); public Column enabled = new Column.BoolInt("enabled"); public Column roster_version = new Column.Text("roster_version") { min_version=2 }; public Column mam_earliest_synced = new Column.Long("mam_earliest_synced") { min_version=4 }; internal AccountTable(Database db) { base(db, "account"); init({id, bare_jid, resourcepart, password, alias, enabled, roster_version, mam_earliest_synced}); } } public class JidTable : Table { public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; public Column bare_jid = new Column.Text("bare_jid") { unique = true, not_null = true }; internal JidTable(Database db) { base(db, "jid"); init({id, bare_jid}); } } public class ContentItemTable : Table { public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; public Column conversation_id = new Column.Integer("conversation_id") { not_null = true }; public Column time = new Column.Long("time") { not_null = true }; public Column local_time = new Column.Long("local_time") { not_null = true }; public Column content_type = new Column.Integer("content_type") { not_null = true }; public Column foreign_id = new Column.Integer("foreign_id") { not_null = true }; public Column hide = new Column.BoolInt("hide") { default = "0", not_null = true, min_version = 9 }; internal ContentItemTable(Database db) { base(db, "content_item"); init({id, conversation_id, time, local_time, content_type, foreign_id, hide}); index("contentitem_localtime_counterpart_idx", {local_time, conversation_id}); unique({content_type, foreign_id}, "IGNORE"); } } public class MessageTable : Table { public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; public Column stanza_id = new Column.Text("stanza_id"); public Column server_id = new Column.Text("server_id") { min_version=10 }; public Column account_id = new Column.Integer("account_id") { not_null = true }; public Column counterpart_id = new Column.Integer("counterpart_id") { not_null = true }; public Column counterpart_resource = new Column.Text("counterpart_resource"); public Column our_resource = new Column.Text("our_resource"); public Column direction = new Column.BoolInt("direction") { not_null = true }; public Column type_ = new Column.Integer("type"); public Column time = new Column.Long("time"); public Column local_time = new Column.Long("local_time"); public Column body = new Column.Text("body"); public Column encryption = new Column.Integer("encryption"); public Column marked = new Column.Integer("marked"); internal MessageTable(Database db) { base(db, "message"); init({id, stanza_id, server_id, account_id, counterpart_id, our_resource, counterpart_resource, direction, type_, time, local_time, body, encryption, marked}); // get latest messages index("message_account_counterpart_localtime_idx", {account_id, counterpart_id, local_time}); // deduplication index("message_account_counterpart_stanzaid_idx", {account_id, counterpart_id, stanza_id}); fts({body}); } } public class RealJidTable : Table { public Column message_id = new Column.Integer("message_id") { primary_key = true }; public Column real_jid = new Column.Text("real_jid"); internal RealJidTable(Database db) { base(db, "real_jid"); init({message_id, real_jid}); } } public class UndecryptedTable : Table { public Column message_id = new Column.Integer("message_id"); public Column type_ = new Column.Integer("type"); public Column data = new Column.Text("data"); internal UndecryptedTable(Database db) { base(db, "undecrypted"); init({message_id, type_, data}); } } public class FileTransferTable : Table { public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; public Column account_id = new Column.Integer("account_id") { not_null = true }; public Column counterpart_id = new Column.Integer("counterpart_id") { not_null = true }; public Column counterpart_resource = new Column.Text("counterpart_resource"); public Column our_resource = new Column.Text("our_resource"); public Column direction = new Column.BoolInt("direction") { not_null = true }; public Column time = new Column.Long("time"); public Column local_time = new Column.Long("local_time"); public Column encryption = new Column.Integer("encryption"); public Column file_name = new Column.Text("file_name"); public Column path = new Column.Text("path"); public Column mime_type = new Column.Text("mime_type"); public Column size = new Column.Integer("size"); public Column state = new Column.Integer("state"); public Column provider = new Column.Integer("provider"); public Column info = new Column.Text("info"); internal FileTransferTable(Database db) { base(db, "file_transfer"); init({id, account_id, counterpart_id, counterpart_resource, our_resource, direction, time, local_time, encryption, file_name, path, mime_type, size, state, provider, info}); index("filetransfer_localtime_counterpart_idx", {local_time, counterpart_id}); } } public class ConversationTable : Table { public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; public Column account_id = new Column.Integer("account_id") { not_null = true }; public Column jid_id = new Column.Integer("jid_id") { not_null = true }; public Column resource = new Column.Text("resource") { min_version=1 }; public Column active = new Column.BoolInt("active"); public Column last_active = new Column.Long("last_active"); public Column type_ = new Column.Integer("type"); public Column encryption = new Column.Integer("encryption"); public Column read_up_to = new Column.Integer("read_up_to"); public Column notification = new Column.Integer("notification") { min_version=3 }; public Column send_typing = new Column.Integer("send_typing") { min_version=3 }; public Column send_marker = new Column.Integer("send_marker") { min_version=3 }; internal ConversationTable(Database db) { base(db, "conversation"); init({id, account_id, jid_id, resource, active, last_active, type_, encryption, read_up_to, notification, send_typing, send_marker}); } } public class AvatarTable : Table { public Column jid = new Column.Text("jid"); public Column hash = new Column.Text("hash"); public Column type_ = new Column.Integer("type"); internal AvatarTable(Database db) { base(db, "avatar"); init({jid, hash, type_}); } } public class EntityFeatureTable : Table { public Column entity = new Column.Text("entity"); public Column feature = new Column.Text("feature"); internal EntityFeatureTable(Database db) { base(db, "entity_feature"); init({entity, feature}); unique({entity, feature}, "IGNORE"); index("entity_feature_idx", {entity}); } } public class RosterTable : Table { public Column account_id = new Column.Integer("account_id"); public Column jid = new Column.Text("jid"); public Column handle = new Column.Text("name"); public Column subscription = new Column.Text("subscription"); internal RosterTable(Database db) { base(db, "roster"); init({account_id, jid, handle, subscription}); unique({account_id, jid}, "IGNORE"); } } public class MamCatchupTable : Table { public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; public Column account_id = new Column.Integer("account_id") { not_null = true }; public Column from_end = new Column.BoolInt("from_end"); public Column from_id = new Column.Text("from_id"); public Column from_time = new Column.Long("from_time") { not_null = true }; public Column to_id = new Column.Text("to_id"); public Column to_time = new Column.Long("to_time") { not_null = true }; internal MamCatchupTable(Database db) { base(db, "mam_catchup"); init({id, account_id, from_end, from_id, from_time, to_id, to_time}); } } public class SettingsTable : Table { public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; public Column key = new Column.Text("key") { unique = true, not_null = true }; public Column value = new Column.Text("value"); internal SettingsTable(Database db) { base(db, "settings"); init({id, key, value}); } } public AccountTable account { get; private set; } public JidTable jid { get; private set; } public ContentItemTable content_item { get; private set; } public MessageTable message { get; private set; } public RealJidTable real_jid { get; private set; } public FileTransferTable file_transfer { get; private set; } public ConversationTable conversation { get; private set; } public AvatarTable avatar { get; private set; } public EntityFeatureTable entity_feature { get; private set; } public RosterTable roster { get; private set; } public MamCatchupTable mam_catchup { get; private set; } public SettingsTable settings { get; private set; } public Map jid_table_cache = new HashMap(); public Map jid_table_reverse = new HashMap(Jid.hash_func, Jid.equals_func); public Map account_table_cache = new HashMap(); public Database(string fileName) { base(fileName, VERSION); account = new AccountTable(this); jid = new JidTable(this); content_item = new ContentItemTable(this); message = new MessageTable(this); real_jid = new RealJidTable(this); file_transfer = new FileTransferTable(this); conversation = new ConversationTable(this); avatar = new AvatarTable(this); entity_feature = new EntityFeatureTable(this); roster = new RosterTable(this); mam_catchup = new MamCatchupTable(this); settings = new SettingsTable(this); init({ account, jid, content_item, message, real_jid, file_transfer, conversation, avatar, entity_feature, roster, mam_catchup, settings }); try { exec("PRAGMA synchronous=0"); } catch (Error e) { } } public override void migrate(long oldVersion) { // new table columns are added, outdated columns are still present if (oldVersion < 7) { message.fts_rebuild(); } if (oldVersion < 8) { try { exec(""" insert into content_item (conversation_id, time, local_time, content_type, foreign_id, hide) select conversation.id, message.time, message.local_time, 1, message.id, 0 from message join conversation on message.account_id=conversation.account_id and message.counterpart_id=conversation.jid_id and message.type=conversation.type+1 and (message.counterpart_resource=conversation.resource or message.type != 3) where message.body not in (select info from file_transfer where info not null) and message.id not in (select info from file_transfer where info not null) union select conversation.id, message.time, message.local_time, 2, file_transfer.id, 0 from file_transfer join message on file_transfer.info=message.id join conversation on file_transfer.account_id=conversation.account_id and file_transfer.counterpart_id=conversation.jid_id and message.type=conversation.type+1 and (message.counterpart_resource=conversation.resource or message.type != 3)"""); } catch (Error e) { error("Failed to upgrade to database version 8: %s", e.message); } } if (oldVersion < 9) { try { exec(""" insert into content_item (conversation_id, time, local_time, content_type, foreign_id, hide) select conversation.id, message.time, message.local_time, 1, message.id, 1 from message join conversation on message.account_id=conversation.account_id and message.counterpart_id=conversation.jid_id and message.type=conversation.type+1 and (message.counterpart_resource=conversation.resource or message.type != 3) where message.body in (select info from file_transfer where info not null) or message.id in (select info from file_transfer where info not null)"""); } catch (Error e) { error("Failed to upgrade to database version 9: %s", e.message); } } if (oldVersion < 11) { try { exec(""" insert into mam_catchup (account_id, from_end, from_time, to_time) select id, 1, 0, mam_earliest_synced from account where mam_earliest_synced not null and mam_earliest_synced > 0"""); } catch (Error e) { error("Failed to upgrade to database version 11: %s", e.message); } } } public ArrayList get_accounts() { ArrayList ret = new ArrayList(Account.equals_func); foreach(Row row in account.select()) { try { Account account = new Account.from_row(this, row); ret.add(account); account_table_cache[account.id] = account; } catch (InvalidJidError e) { warning("Ignoring account with invalid Jid: %s", e.message); } } return ret; } public Account? get_account_by_id(int id) { if (account_table_cache.has_key(id)) { return account_table_cache[id]; } else { Row? row = account.row_with(account.id, id).inner; if (row != null) { try { Account a = new Account.from_row(this, row); account_table_cache[a.id] = a; return a; } catch (InvalidJidError e) { warning("Ignoring account with invalid Jid: %s", e.message); } } return null; } } public int add_content_item(Conversation conversation, DateTime time, DateTime local_time, int content_type, int foreign_id, bool hide) { return (int) content_item.insert() .value(content_item.conversation_id, conversation.id) .value(content_item.local_time, (long) local_time.to_unix()) .value(content_item.time, (long) time.to_unix()) .value(content_item.content_type, content_type) .value(content_item.foreign_id, foreign_id) .value(content_item.hide, hide) .perform(); } public Gee.List get_messages(Jid jid, Account account, Message.Type? type, int count, DateTime? before, DateTime? after, int id) { QueryBuilder select = message.select(); if (before != null) { if (id > 0) { select.where(@"local_time < ? OR (local_time = ? AND id < ?)", { before.to_unix().to_string(), before.to_unix().to_string(), id.to_string() }); } else { select.with(message.id, "<", id); } } if (after != null) { if (id > 0) { select.where(@"local_time > ? OR (local_time = ? AND id > ?)", { after.to_unix().to_string(), after.to_unix().to_string(), id.to_string() }); } else { select.with(message.local_time, ">", (long) after.to_unix()); } if (id > 0) { select.with(message.id, ">", id); } } else { select.order_by(message.local_time, "DESC"); } select.with(message.counterpart_id, "=", get_jid_id(jid)) .with(message.account_id, "=", account.id) .limit(count); if (jid.resourcepart != null) { select.with(message.counterpart_resource, "=", jid.resourcepart); } if (type != null) { select.with(message.type_, "=", (int) type); } select.outer_join_with(real_jid, real_jid.message_id, message.id); LinkedList ret = new LinkedList(); foreach (Row row in select) { try { ret.insert(0, new Message.from_row(this, row)); } catch (InvalidJidError e) { warning("Ignoring message with invalid Jid: %s", e.message); } } return ret; } public Gee.List get_unsend_messages(Account account, Jid? jid = null) { Gee.List ret = new ArrayList(); var select = message.select() .with(message.account_id, "=", account.id) .with(message.marked, "=", (int) Message.Marked.UNSENT); if (jid != null) { select.with(message.counterpart_id, "=", get_jid_id(jid)); } foreach (Row row in select) { try { ret.add(new Message.from_row(this, row)); } catch (InvalidJidError e) { warning("Ignoring message with invalid Jid: %s", e.message); } } return ret; } public Message? get_message_by_id(int id) { Row? row = message.row_with(message.id, id).inner; if (row != null) { try { return new Message.from_row(this, row); } catch (InvalidJidError e) { warning("Ignoring message with invalid Jid: %s", e.message); } } return null; } public ArrayList get_conversations(Account account) { ArrayList ret = new ArrayList(); foreach (Row row in conversation.select().with(conversation.account_id, "=", account.id)) { try { ret.add(new Conversation.from_row(this, row)); } catch (InvalidJidError e) { warning("Ignoring conversation with invalid Jid: %s", e.message); } } return ret; } public void set_avatar_hash(Jid jid, string hash, int type) { avatar.insert().or("REPLACE") .value(avatar.jid, jid.to_string()) .value(avatar.hash, hash) .value(avatar.type_, type) .perform(); } public HashMap get_avatar_hashes(int type) { HashMap ret = new HashMap(Jid.hash_func, Jid.equals_func); foreach (Row row in avatar.select({avatar.jid, avatar.hash}).with(avatar.type_, "=", type)) { try { ret[new Jid(row[avatar.jid])] = row[avatar.hash]; } catch (InvalidJidError e) { warning("Ignoring avatar of invalid Jid: %s", e.message); } } return ret; } public void add_entity_features(string entity, Gee.List features) { foreach (string feature in features) { entity_feature.insert() .value(entity_feature.entity, entity) .value(entity_feature.feature, feature) .perform(); } } public Gee.List get_entity_features(string entity) { ArrayList ret = new ArrayList(); foreach (Row row in entity_feature.select({entity_feature.feature}).with(entity_feature.entity, "=", entity)) { ret.add(row[entity_feature.feature]); } return ret; } public int get_jid_id(Jid jid_obj) { var bare_jid = jid_obj.bare_jid; if (jid_table_reverse.has_key(bare_jid)) { return jid_table_reverse[bare_jid]; } else { Row? row = jid.row_with(jid.bare_jid, jid_obj.bare_jid.to_string()).inner; if (row != null) { int id = row[jid.id]; jid_table_cache[id] = bare_jid; jid_table_reverse[bare_jid] = id; return id; } else { return add_jid(jid_obj); } } } public Jid? get_jid_by_id(int id) throws InvalidJidError { if (jid_table_cache.has_key(id)) { return jid_table_cache[id]; } else { string? bare_jid = jid.select({jid.bare_jid}).with(jid.id, "=", id)[jid.bare_jid]; if (bare_jid != null) { Jid jid_parsed = new Jid(bare_jid); jid_table_cache[id] = jid_parsed; // Only store fully normalized Jids for reverse lookup if (jid_parsed.to_string() == bare_jid) { jid_table_reverse[jid_parsed] = id; } return jid_parsed; } return null; } } private int add_jid(Jid jid_obj) { Jid bare_jid = jid_obj.bare_jid; int id = (int) jid.insert().value(jid.bare_jid, bare_jid.to_string()).perform(); jid_table_cache[id] = bare_jid; jid_table_reverse[bare_jid] = id; return id; } } } dino-0.1.0/libdino/src/service/entity_capabilities_storage.vala0000644000000000000000000000074013614354364023436 0ustar rootrootusing Gee; using Xmpp; namespace Dino { public class EntityCapabilitiesStorage : Xep.EntityCapabilities.Storage, Object { private Database db; public EntityCapabilitiesStorage(Database db) { this.db = db; } public void store_features(string entity, Gee.List features) { db.add_entity_features(entity, features); } public Gee.List get_features(string entity) { return db.get_entity_features(entity); } } } dino-0.1.0/libdino/src/service/file_manager.vala0000644000000000000000000004512613614354364020305 0ustar rootrootusing Gdk; using Gee; using Xmpp; using Dino.Entities; namespace Dino { public class FileManager : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("file"); public string id { get { return IDENTITY.id; } } public signal void upload_available(Account account); public signal void received_file(FileTransfer file_transfer, Conversation conversation); private StreamInteractor stream_interactor; private Database db; private Gee.List file_senders = new ArrayList(); private Gee.List file_encryptors = new ArrayList(); private Gee.List file_decryptors = new ArrayList(); private Gee.List file_providers = new ArrayList(); public static void start(StreamInteractor stream_interactor, Database db) { FileManager m = new FileManager(stream_interactor, db); stream_interactor.add_module(m); } public static string get_storage_dir() { return Path.build_filename(Dino.get_storage_dir(), "files"); } private FileManager(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; this.db = db; DirUtils.create_with_parents(get_storage_dir(), 0700); this.add_provider(new JingleFileProvider(stream_interactor)); this.add_sender(new JingleFileSender(stream_interactor)); } public async void send_file(string uri, Conversation conversation) { FileTransfer file_transfer = new FileTransfer(); file_transfer.account = conversation.account; file_transfer.counterpart = conversation.counterpart; if (conversation.type_.is_muc_semantic()) { file_transfer.ourpart = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account) ?? conversation.account.bare_jid; } else { file_transfer.ourpart = conversation.account.full_jid; } file_transfer.direction = FileTransfer.DIRECTION_SENT; file_transfer.time = new DateTime.now_utc(); file_transfer.local_time = new DateTime.now_utc(); file_transfer.encryption = conversation.encryption; try { File file = File.new_for_path(uri); FileInfo file_info = file.query_info("*", FileQueryInfoFlags.NONE); file_transfer.file_name = file_info.get_display_name(); file_transfer.mime_type = file_info.get_content_type(); file_transfer.size = (int)file_info.get_size(); file_transfer.input_stream = yield file.read_async(); yield save_file(file_transfer); file_transfer.persist(db); received_file(file_transfer, conversation); } catch (Error e) { file_transfer.state = FileTransfer.State.FAILED; warning("Error saving outgoing file: %s", e.message); return; } try { var file_meta = new FileMeta(); file_meta.size = file_transfer.size; file_meta.mime_type = file_transfer.mime_type; FileSender file_sender = null; FileEncryptor file_encryptor = null; foreach (FileSender sender in file_senders) { if (sender.can_send(conversation, file_transfer)) { if (file_transfer.encryption == Encryption.NONE || sender.can_encrypt(conversation, file_transfer)) { file_sender = sender; break; } else { foreach (FileEncryptor encryptor in file_encryptors) { if (encryptor.can_encrypt_file(conversation, file_transfer)) { file_encryptor = encryptor; break; } } if (file_encryptor != null) { file_sender = sender; break; } } } } if (file_sender == null) { throw new FileSendError.UPLOAD_FAILED("No sender/encryptor combination available"); } if (file_encryptor != null) { file_meta = file_encryptor.encrypt_file(conversation, file_transfer); } FileSendData file_send_data = yield file_sender.prepare_send_file(conversation, file_transfer, file_meta); if (file_encryptor != null) { file_send_data = file_encryptor.preprocess_send_file(conversation, file_transfer, file_send_data, file_meta); } yield file_sender.send_file(conversation, file_transfer, file_send_data, file_meta); conversation.last_active = file_transfer.time; } catch (Error e) { warning("Send file error: %s", e.message); file_transfer.state = FileTransfer.State.FAILED; } } public async void download_file(FileTransfer file_transfer) { Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(file_transfer.counterpart.bare_jid, file_transfer.account); FileProvider? file_provider = null; foreach (FileProvider fp in file_providers) { if (file_transfer.provider == fp.get_id()) { file_provider = fp; } } yield download_file_internal(file_provider, file_transfer, conversation); } public bool is_upload_available(Conversation? conversation) { if (conversation == null) return false; foreach (FileSender file_sender in file_senders) { if (file_sender.is_upload_available(conversation)) return true; } return false; } public Gee.List get_latest_transfers(Account account, Jid counterpart, int n) { Qlite.QueryBuilder select = db.file_transfer.select() .with(db.file_transfer.counterpart_id, "=", db.get_jid_id(counterpart)) .with(db.file_transfer.account_id, "=", account.id) .order_by(db.file_transfer.local_time, "DESC") .limit(n); return get_transfers_from_qry(select); } public Gee.List get_transfers_before(Account account, Jid counterpart, DateTime before, int n) { Qlite.QueryBuilder select = db.file_transfer.select() .with(db.file_transfer.counterpart_id, "=", db.get_jid_id(counterpart)) .with(db.file_transfer.account_id, "=", account.id) .with(db.file_transfer.local_time, "<", (long)before.to_unix()) .order_by(db.file_transfer.local_time, "DESC") .limit(n); return get_transfers_from_qry(select); } public Gee.List get_transfers_after(Account account, Jid counterpart, DateTime after, int n) { Qlite.QueryBuilder select = db.file_transfer.select() .with(db.file_transfer.counterpart_id, "=", db.get_jid_id(counterpart)) .with(db.file_transfer.account_id, "=", account.id) .with(db.file_transfer.local_time, ">", (long)after.to_unix()) .limit(n); return get_transfers_from_qry(select); } private Gee.List get_transfers_from_qry(Qlite.QueryBuilder select) { Gee.List ret = new ArrayList(); foreach (Qlite.Row row in select) { try { FileTransfer file_transfer = new FileTransfer.from_row(db, row, get_storage_dir()); ret.insert(0, file_transfer); } catch (InvalidJidError e) { warning("Ignoring file transfer with invalid Jid: %s", e.message); } } return ret; } public void add_provider(FileProvider file_provider) { file_providers.add(file_provider); file_provider.file_incoming.connect((info, from, time, local_time, conversation, receive_data, file_meta) => { handle_incoming_file.begin(file_provider, info, from, time, local_time, conversation, receive_data, file_meta); }); } public void add_sender(FileSender file_sender) { file_senders.add(file_sender); file_sender.upload_available.connect((account) => { upload_available(account); }); file_senders.sort((a, b) => { return (int) (b.get_priority() - a.get_priority()); }); } public void add_file_encryptor(FileEncryptor encryptor) { file_encryptors.add(encryptor); } public void add_file_decryptor(FileDecryptor decryptor) { file_decryptors.add(decryptor); } public bool is_sender_trustworthy(FileTransfer file_transfer, Conversation conversation) { Jid relevant_jid = stream_interactor.get_module(MucManager.IDENTITY).get_real_jid(file_transfer.from, conversation.account) ?? conversation.counterpart; bool in_roster = stream_interactor.get_module(RosterManager.IDENTITY).get_roster_item(conversation.account, relevant_jid) != null; return file_transfer.direction == FileTransfer.DIRECTION_SENT || in_roster; } private async FileMeta get_file_meta(FileProvider file_provider, FileTransfer file_transfer, Conversation conversation, FileReceiveData receive_data_) throws FileReceiveError { FileReceiveData receive_data = receive_data_; FileMeta file_meta = file_provider.get_file_meta(file_transfer); if (file_meta.size == -1) { foreach (FileDecryptor file_decryptor in file_decryptors) { if (file_decryptor.can_decrypt_file(conversation, file_transfer, receive_data)) { receive_data = file_decryptor.prepare_get_meta_info(conversation, file_transfer, receive_data); break; } } file_meta = yield file_provider.get_meta_info(file_transfer, receive_data, file_meta); file_transfer.size = (int)file_meta.size; file_transfer.file_name = file_meta.file_name; file_transfer.mime_type = file_meta.mime_type; } return file_meta; } private async void download_file_internal(FileProvider file_provider, FileTransfer file_transfer, Conversation conversation) { try { // Get meta info FileReceiveData receive_data = file_provider.get_file_receive_data(file_transfer); FileDecryptor? file_decryptor = null; foreach (FileDecryptor decryptor in file_decryptors) { if (decryptor.can_decrypt_file(conversation, file_transfer, receive_data)) { file_decryptor = decryptor; break; } } if (file_decryptor != null) { receive_data = file_decryptor.prepare_get_meta_info(conversation, file_transfer, receive_data); } FileMeta file_meta = yield get_file_meta(file_provider, file_transfer, conversation, receive_data); InputStream? input_stream = null; // Download and decrypt file file_transfer.state = FileTransfer.State.IN_PROGRESS; if (file_decryptor != null) { file_meta = file_decryptor.prepare_download_file(conversation, file_transfer, receive_data, file_meta); } input_stream = yield file_provider.download(file_transfer, receive_data, file_meta); if (file_decryptor != null) { input_stream = yield file_decryptor.decrypt_file(input_stream, conversation, file_transfer, receive_data); } // Save file string filename = Random.next_int().to_string("%x") + "_" + file_transfer.file_name; File file = File.new_for_path(Path.build_filename(get_storage_dir(), filename)); OutputStream os = file.create(FileCreateFlags.REPLACE_DESTINATION); yield os.splice_async(input_stream, OutputStreamSpliceFlags.CLOSE_SOURCE|OutputStreamSpliceFlags.CLOSE_TARGET); file_transfer.path = file.get_basename(); file_transfer.input_stream = yield file.read_async(); FileInfo file_info = file_transfer.get_file().query_info("*", FileQueryInfoFlags.NONE); file_transfer.mime_type = file_info.get_content_type(); file_transfer.state = FileTransfer.State.COMPLETE; } catch (Error e) { warning("Error downloading file: %s", e.message); file_transfer.state = FileTransfer.State.FAILED; } } private async void handle_incoming_file(FileProvider file_provider, string info, Jid from, DateTime time, DateTime local_time, Conversation conversation, FileReceiveData receive_data, FileMeta file_meta) { FileTransfer file_transfer = new FileTransfer(); file_transfer.account = conversation.account; file_transfer.direction = from.bare_jid.equals(conversation.account.bare_jid) ? FileTransfer.DIRECTION_SENT : FileTransfer.DIRECTION_RECEIVED; file_transfer.counterpart = file_transfer.direction == FileTransfer.DIRECTION_RECEIVED ? from : conversation.counterpart; if (conversation.type_.is_muc_semantic()) { file_transfer.ourpart = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account) ?? conversation.account.bare_jid; } else { file_transfer.ourpart = conversation.account.full_jid; } file_transfer.time = time; file_transfer.local_time = local_time; file_transfer.provider = file_provider.get_id(); file_transfer.file_name = file_meta.file_name; file_transfer.size = (int)file_meta.size; file_transfer.info = info; file_transfer.persist(db); if (is_sender_trustworthy(file_transfer, conversation)) { try { yield get_file_meta(file_provider, file_transfer, conversation, receive_data); if (file_transfer.size >= 0 && file_transfer.size < 5000000) { yield download_file_internal(file_provider, file_transfer, conversation); } } catch (Error e) { warning("Error downloading file: %s", e.message); file_transfer.state = FileTransfer.State.FAILED; } } conversation.last_active = file_transfer.time; received_file(file_transfer, conversation); } private async void save_file(FileTransfer file_transfer) throws FileSendError { try { string filename = Random.next_int().to_string("%x") + "_" + file_transfer.file_name; File file = File.new_for_path(Path.build_filename(get_storage_dir(), filename)); OutputStream os = file.create(FileCreateFlags.REPLACE_DESTINATION); yield os.splice_async(file_transfer.input_stream, OutputStreamSpliceFlags.CLOSE_SOURCE|OutputStreamSpliceFlags.CLOSE_TARGET); file_transfer.state = FileTransfer.State.COMPLETE; file_transfer.path = filename; file_transfer.input_stream = yield file.read_async(); } catch (Error e) { throw new FileSendError.SAVE_FAILED("Saving file error: %s".printf(e.message)); } } } public errordomain FileSendError { ENCRYPTION_FAILED, UPLOAD_FAILED, SAVE_FAILED } public errordomain FileReceiveError { GET_METADATA_FAILED, DECRYPTION_FAILED, DOWNLOAD_FAILED } public class FileMeta { public int64 size = -1; public string? mime_type = null; public string? file_name = null; public Encryption encryption = Encryption.NONE; } public class HttpFileMeta : FileMeta { public Message message; } public class FileSendData { } public class HttpFileSendData : FileSendData { public string url_down { get; set; } public string url_up { get; set; } public HashMap headers { get; set; } public bool encrypt_message { get; set; default=true; } } public class FileReceiveData { } public class HttpFileReceiveData : FileReceiveData { public string url { get; set; } } public interface FileProvider : Object { public signal void file_incoming(string info, Jid from, DateTime time, DateTime local_time, Conversation conversation, FileReceiveData receive_data, FileMeta file_meta); public abstract FileMeta get_file_meta(FileTransfer file_transfer) throws FileReceiveError; public abstract FileReceiveData? get_file_receive_data(FileTransfer file_transfer); public abstract async FileMeta get_meta_info(FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta) throws FileReceiveError; public abstract async InputStream download(FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta) throws FileReceiveError; public abstract int get_id(); } public interface FileSender : Object { public signal void upload_available(Account account); public abstract bool is_upload_available(Conversation conversation); public abstract bool can_send(Conversation conversation, FileTransfer file_transfer); public abstract async FileSendData? prepare_send_file(Conversation conversation, FileTransfer file_transfer, FileMeta file_meta) throws FileSendError; public abstract async void send_file(Conversation conversation, FileTransfer file_transfer, FileSendData file_send_data, FileMeta file_meta) throws FileSendError; public abstract bool can_encrypt(Conversation conversation, FileTransfer file_transfer); public abstract int get_id(); public abstract float get_priority(); } public interface FileEncryptor : Object { public abstract bool can_encrypt_file(Conversation conversation, FileTransfer file_transfer); public abstract FileMeta encrypt_file(Conversation conversation, FileTransfer file_transfer) throws FileSendError; public abstract FileSendData? preprocess_send_file(Conversation conversation, FileTransfer file_transfer, FileSendData file_send_data, FileMeta file_meta) throws FileSendError; } public interface FileDecryptor : Object { public abstract FileReceiveData prepare_get_meta_info(Conversation conversation, FileTransfer file_transfer, FileReceiveData receive_data); public abstract FileMeta prepare_download_file(Conversation conversation, FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta); public abstract bool can_decrypt_file(Conversation conversation, FileTransfer file_transfer, FileReceiveData receive_data); public abstract async InputStream decrypt_file(InputStream encrypted_stream, Conversation conversation, FileTransfer file_transfer, FileReceiveData receive_data) throws FileReceiveError; } } dino-0.1.0/libdino/src/service/jingle_file_transfers.vala0000644000000000000000000002327113614354364022227 0ustar rootrootusing Gdk; using Gee; using Xmpp; using Dino.Entities; namespace Dino { public interface JingleFileEncryptionHelper : Object { public abstract bool can_transfer(Conversation conversation); public abstract bool can_encrypt(Conversation conversation, FileTransfer file_transfer, Jid? full_jid = null); public abstract string? get_precondition_name(Conversation conversation, FileTransfer file_transfer); public abstract Object? get_precondition_options(Conversation conversation, FileTransfer file_transfer); public abstract FileMeta complete_meta(FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta, Xmpp.Xep.JingleFileTransfer.FileTransfer jingle_transfer); } public class JingleFileEncryptionHelperTransferOnly : JingleFileEncryptionHelper, Object { public bool can_transfer(Conversation conversation) { return true; } public bool can_encrypt(Conversation conversation, FileTransfer file_transfer, Jid? full_jid) { return false; } public string? get_precondition_name(Conversation conversation, FileTransfer file_transfer) { return null; } public Object? get_precondition_options(Conversation conversation, FileTransfer file_transfer) { return null; } public FileMeta complete_meta(FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta, Xmpp.Xep.JingleFileTransfer.FileTransfer jingle_transfer) { return file_meta; } } public class JingleFileHelperRegistry { private static JingleFileHelperRegistry INSTANCE; public static JingleFileHelperRegistry instance { get { if (INSTANCE == null) { INSTANCE = new JingleFileHelperRegistry(); INSTANCE.add_encryption_helper(Encryption.NONE, new JingleFileEncryptionHelperTransferOnly()); } return INSTANCE; } } internal HashMap encryption_helpers = new HashMap(); public void add_encryption_helper(Encryption encryption, JingleFileEncryptionHelper helper) { encryption_helpers[encryption] = helper; } public JingleFileEncryptionHelper? get_encryption_helper(Encryption encryption) { if (encryption_helpers.has_key(encryption)) { return encryption_helpers[encryption]; } return null; } } public class JingleFileProvider : FileProvider, Object { private StreamInteractor stream_interactor; private HashMap file_transfers = new HashMap(); public JingleFileProvider(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; stream_interactor.stream_negotiated.connect(on_stream_negotiated); } public FileMeta get_file_meta(FileTransfer file_transfer) throws FileReceiveError { var file_meta = new FileMeta(); file_meta.file_name = file_transfer.file_name; file_meta.size = file_transfer.size; return file_meta; } public FileReceiveData? get_file_receive_data(FileTransfer file_transfer) { return new FileReceiveData(); } public async FileMeta get_meta_info(FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta) throws FileReceiveError { Xmpp.Xep.JingleFileTransfer.FileTransfer? jingle_file_transfer = file_transfers[file_transfer.info]; if (jingle_file_transfer == null) { throw new FileReceiveError.DOWNLOAD_FAILED("Transfer data not available anymore"); } FileMeta meta = file_meta; foreach (JingleFileEncryptionHelper helper in JingleFileHelperRegistry.instance.encryption_helpers.values) { meta = helper.complete_meta(file_transfer, receive_data, meta, jingle_file_transfer); } return meta; } public async InputStream download(FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta) throws FileReceiveError { // TODO(hrxi) What should happen if `stream == null`? XmppStream? stream = stream_interactor.get_stream(file_transfer.account); Xmpp.Xep.JingleFileTransfer.FileTransfer? jingle_file_transfer = file_transfers[file_transfer.info]; if (jingle_file_transfer == null) { throw new FileReceiveError.DOWNLOAD_FAILED("Transfer data not available anymore"); } foreach (JingleFileEncryptionHelper helper in JingleFileHelperRegistry.instance.encryption_helpers.values) { helper.complete_meta(file_transfer, receive_data, file_meta, jingle_file_transfer); } try { jingle_file_transfer.accept(stream); } catch (IOError e) { throw new FileReceiveError.DOWNLOAD_FAILED("Establishing connection did not work"); } return jingle_file_transfer.stream; } public int get_id() { return 1; } private void on_stream_negotiated(Account account, XmppStream stream) { stream_interactor.module_manager.get_module(account, Xmpp.Xep.JingleFileTransfer.Module.IDENTITY).file_incoming.connect((stream, jingle_file_transfer) => { Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(jingle_file_transfer.peer.bare_jid, account); if (conversation == null) { // TODO(hrxi): What to do? return; } string id = random_uuid(); file_transfers[id] = jingle_file_transfer; FileMeta file_meta = new FileMeta(); file_meta.size = jingle_file_transfer.size; file_meta.file_name = jingle_file_transfer.file_name; var time = new DateTime.now_utc(); var from = jingle_file_transfer.peer.bare_jid; file_incoming(id, from, time, time, conversation, new FileReceiveData(), file_meta); }); } } public class JingleFileSender : FileSender, Object { private StreamInteractor stream_interactor; public JingleFileSender(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public bool is_upload_available(Conversation conversation) { JingleFileEncryptionHelper? helper = JingleFileHelperRegistry.instance.get_encryption_helper(conversation.encryption); if (helper == null) return false; if (!helper.can_transfer(conversation)) return false; XmppStream? stream = stream_interactor.get_stream(conversation.account); if (stream == null) return false; Gee.List? resources = stream.get_flag(Presence.Flag.IDENTITY).get_resources(conversation.counterpart); if (resources == null) return false; foreach (Jid full_jid in resources) { if (stream.get_module(Xep.JingleFileTransfer.Module.IDENTITY).is_available(stream, full_jid)) { return true; } } return false; } public bool can_send(Conversation conversation, FileTransfer file_transfer) { if (conversation.type_ == Conversation.Type.GROUPCHAT) return false; // No file specific restrictions apply to Jingle file transfers return is_upload_available(conversation); } public bool can_encrypt(Conversation conversation, FileTransfer file_transfer) { JingleFileEncryptionHelper? helper = JingleFileHelperRegistry.instance.get_encryption_helper(file_transfer.encryption); if (helper == null) return false; return helper.can_encrypt(conversation, file_transfer); } public async FileSendData? prepare_send_file(Conversation conversation, FileTransfer file_transfer, FileMeta file_meta) throws FileSendError { if (file_meta is HttpFileMeta) { throw new FileSendError.UPLOAD_FAILED("Cannot upload http file meta over Jingle"); } return new FileSendData(); } public async void send_file(Conversation conversation, FileTransfer file_transfer, FileSendData file_send_data, FileMeta file_meta) throws FileSendError { XmppStream? stream = stream_interactor.get_stream(file_transfer.account); if (stream == null) throw new FileSendError.UPLOAD_FAILED("No stream available"); JingleFileEncryptionHelper? helper = JingleFileHelperRegistry.instance.get_encryption_helper(file_transfer.encryption); bool must_encrypt = helper != null && helper.can_encrypt(conversation, file_transfer); foreach (Jid full_jid in stream.get_flag(Presence.Flag.IDENTITY).get_resources(conversation.counterpart)) { // TODO(hrxi): Prioritization of transports (and resources?). if (!stream.get_module(Xep.JingleFileTransfer.Module.IDENTITY).is_available(stream, full_jid)) { continue; } if (must_encrypt && !helper.can_encrypt(conversation, file_transfer, full_jid)) { continue; } string? precondition_name = null; Object? precondition_options = null; if (must_encrypt) { precondition_name = helper.get_precondition_name(conversation, file_transfer); precondition_options = helper.get_precondition_options(conversation, file_transfer); if (precondition_name == null) { throw new FileSendError.ENCRYPTION_FAILED("Should have created a precondition, but did not"); } } yield stream.get_module(Xep.JingleFileTransfer.Module.IDENTITY).offer_file_stream(stream, full_jid, file_transfer.input_stream, file_transfer.server_file_name, file_meta.size, precondition_name, precondition_options); return; } } public int get_id() { return 1; } public float get_priority() { return 50; } } } dino-0.1.0/libdino/src/service/message_processor.vala0000644000000000000000000007532613614354364021424 0ustar rootrootusing Gee; using Xmpp; using Xmpp.Xep; using Dino.Entities; using Qlite; namespace Dino { public class MessageProcessor : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("message_processor"); public string id { get { return IDENTITY.id; } } public signal void message_received(Entities.Message message, Conversation conversation); public signal void build_message_stanza(Entities.Message message, Xmpp.MessageStanza message_stanza, Conversation conversation); public signal void pre_message_send(Entities.Message message, Xmpp.MessageStanza message_stanza, Conversation conversation); public signal void message_sent(Entities.Message message, Conversation conversation); public signal void history_synced(Account account); public MessageListenerHolder received_pipeline = new MessageListenerHolder(); private StreamInteractor stream_interactor; private Database db; private Object lock_send_unsent; private HashMap current_catchup_id = new HashMap(Account.hash_func, Account.equals_func); private HashMap> mam_times = new HashMap>(); public HashMap hitted_range = new HashMap(); public HashMap catchup_until_id = new HashMap(Account.hash_func, Account.equals_func); public HashMap catchup_until_time = new HashMap(Account.hash_func, Account.equals_func); public static void start(StreamInteractor stream_interactor, Database db) { MessageProcessor m = new MessageProcessor(stream_interactor, db); stream_interactor.add_module(m); } private MessageProcessor(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; this.db = db; received_pipeline.connect(new DeduplicateMessageListener(this, db)); received_pipeline.connect(new FilterMessageListener()); received_pipeline.connect(new StoreMessageListener(stream_interactor)); received_pipeline.connect(new MamMessageListener(stream_interactor)); stream_interactor.account_added.connect(on_account_added); stream_interactor.connection_manager.connection_state_changed.connect((account, state) => { if (state == ConnectionManager.ConnectionState.CONNECTED) send_unsent_messages(account); }); stream_interactor.connection_manager.stream_opened.connect((account, stream) => { debug("MAM: [%s] Reset catchup_id", account.bare_jid.to_string()); current_catchup_id.unset(account); mam_times[account] = new HashMap(); }); } public Entities.Message send_text(string text, Conversation conversation) { Entities.Message message = create_out_message(text, conversation); return send_message(message, conversation); } public Entities.Message send_message(Entities.Message message, Conversation conversation) { stream_interactor.get_module(MessageStorage.IDENTITY).add_message(message, conversation); send_xmpp_message(message, conversation); message_sent(message, conversation); return message; } public void send_unsent_messages(Account account, Jid? jid = null) { Gee.List unsend_messages = db.get_unsend_messages(account, jid); foreach (Entities.Message message in unsend_messages) { Conversation? msg_conv = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(message.counterpart, account); if (msg_conv != null) { send_xmpp_message(message, msg_conv, true); } } } private void on_account_added(Account account) { stream_interactor.module_manager.get_module(account, Xmpp.MessageModule.IDENTITY).received_message.connect( (stream, message) => { on_message_received.begin(account, message); }); XmppStream? stream_bak = null; stream_interactor.module_manager.get_module(account, Xmpp.Xep.MessageArchiveManagement.Module.IDENTITY).feature_available.connect( (stream) => { if (stream == stream_bak) return; current_catchup_id.unset(account); stream_bak = stream; debug("MAM: [%s] MAM available", account.bare_jid.to_string()); do_mam_catchup.begin(account); }); stream_interactor.module_manager.get_module(account, Xmpp.MessageModule.IDENTITY).received_message_unprocessed.connect((stream, message) => { if (!message.from.equals(account.bare_jid)) return; Xep.MessageArchiveManagement.Flag? mam_flag = stream != null ? stream.get_flag(Xep.MessageArchiveManagement.Flag.IDENTITY) : null; if (mam_flag == null) return; string? id = message.stanza.get_deep_attribute(mam_flag.ns_ver + ":result", "id"); if (id == null) return; StanzaNode? delay_node = message.stanza.get_deep_subnode(mam_flag.ns_ver + ":result", "urn:xmpp:forward:0:forwarded", "urn:xmpp:delay:delay"); if (delay_node == null) return; DateTime? time = DelayedDelivery.Module.get_time_for_node(delay_node); if (time == null) return; mam_times[account][id] = time; string? query_id = message.stanza.get_deep_attribute(mam_flag.ns_ver + ":result", mam_flag.ns_ver + ":queryid"); if (query_id != null && id == catchup_until_id[account]) { debug("MAM: [%s] Hitted range (id) %s", account.bare_jid.to_string(), id); hitted_range[query_id] = -2; } }); } private async void do_mam_catchup(Account account) { debug("MAM: [%s] Start catchup", account.bare_jid.to_string()); string? earliest_id = null; DateTime? earliest_time = null; bool continue_sync = true; while (continue_sync) { continue_sync = false; // Get previous row var previous_qry = db.mam_catchup.select().with(db.mam_catchup.account_id, "=", account.id).order_by(db.mam_catchup.to_time, "DESC"); if (current_catchup_id.has_key(account)) { previous_qry.with(db.mam_catchup.id, "!=", current_catchup_id[account]); } RowOption previous_row = previous_qry.single().row(); if (previous_row.is_present()) { catchup_until_id[account] = previous_row[db.mam_catchup.to_id]; catchup_until_time[account] = (new DateTime.from_unix_utc(previous_row[db.mam_catchup.to_time])).add_minutes(-5); debug("MAM: [%s] Previous entry exists", account.bare_jid.to_string()); } else { catchup_until_id.unset(account); catchup_until_time.unset(account); } string query_id = Xmpp.random_uuid(); yield get_mam_range(account, query_id, null, null, earliest_time, earliest_id); if (!hitted_range.has_key(query_id)) { debug("MAM: [%s] Set catchup end reached", account.bare_jid.to_string()); db.mam_catchup.update() .set(db.mam_catchup.from_end, true) .with(db.mam_catchup.id, "=", current_catchup_id[account]) .perform(); } if (hitted_range.has_key(query_id)) { if (merge_ranges(account, null)) { RowOption current_row = db.mam_catchup.row_with(db.mam_catchup.id, current_catchup_id[account]); bool range_from_complete = current_row[db.mam_catchup.from_end]; if (!range_from_complete) { continue_sync = true; earliest_id = current_row[db.mam_catchup.from_id]; earliest_time = (new DateTime.from_unix_utc(current_row[db.mam_catchup.from_time])).add_seconds(1); } } } } } /* * Merges the row with `current_catchup_id` with the previous range (optional: with `earlier_id`) * Changes `current_catchup_id` to the previous range */ private bool merge_ranges(Account account, int? earlier_id) { RowOption current_row = db.mam_catchup.row_with(db.mam_catchup.id, current_catchup_id[account]); RowOption previous_row = null; if (earlier_id != null) { previous_row = db.mam_catchup.row_with(db.mam_catchup.id, earlier_id); } else { previous_row = db.mam_catchup.select() .with(db.mam_catchup.account_id, "=", account.id) .with(db.mam_catchup.id, "!=", current_catchup_id[account]) .order_by(db.mam_catchup.to_time, "DESC").single().row(); } if (!previous_row.is_present()) { debug("MAM: [%s] Merging: No previous row", account.bare_jid.to_string()); return false; } var qry = db.mam_catchup.update().with(db.mam_catchup.id, "=", previous_row[db.mam_catchup.id]); debug("MAM: [%s] Merging %ld-%ld with %ld- %ld", account.bare_jid.to_string(), previous_row[db.mam_catchup.from_time], previous_row[db.mam_catchup.to_time], current_row[db.mam_catchup.from_time], current_row[db.mam_catchup.to_time]); if (current_row[db.mam_catchup.from_time] < previous_row[db.mam_catchup.from_time]) { qry.set(db.mam_catchup.from_id, current_row[db.mam_catchup.from_id]) .set(db.mam_catchup.from_time, current_row[db.mam_catchup.from_time]); } if (current_row[db.mam_catchup.to_time] > previous_row[db.mam_catchup.to_time]) { qry.set(db.mam_catchup.to_id, current_row[db.mam_catchup.to_id]) .set(db.mam_catchup.to_time, current_row[db.mam_catchup.to_time]); } qry.perform(); current_catchup_id[account] = previous_row[db.mam_catchup.id]; db.mam_catchup.delete().with(db.mam_catchup.id, "=", current_row[db.mam_catchup.id]).perform(); return true; } private async bool get_mam_range(Account account, string? query_id, DateTime? from_time, string? from_id, DateTime? to_time, string? to_id) { debug("MAM: [%s] Get range %s - %s", account.bare_jid.to_string(), from_time != null ? from_time.to_string() : "", to_time != null ? to_time.to_string() : ""); XmppStream stream = stream_interactor.get_stream(account); Iq.Stanza? iq = yield stream.get_module(Xep.MessageArchiveManagement.Module.IDENTITY).query_archive(stream, null, query_id, from_time, from_id, to_time, to_id); if (iq == null) { debug(@"MAM: [%s] IQ null", account.bare_jid.to_string()); return true; } if (iq.stanza.get_deep_string_content("urn:xmpp:mam:2:fin", "http://jabber.org/protocol/rsm" + ":set", "first") == null) { return true; } while (iq != null) { string? earliest_id = iq.stanza.get_deep_string_content("urn:xmpp:mam:2:fin", "http://jabber.org/protocol/rsm" + ":set", "first"); if (earliest_id == null) return true; if (!mam_times[account].has_key(earliest_id)) error("wtf"); debug("MAM: [%s] Update from_id %s", account.bare_jid.to_string(), earliest_id); if (!current_catchup_id.has_key(account)) { debug("MAM: [%s] We get our first MAM page", account.bare_jid.to_string()); string? latest_id = iq.stanza.get_deep_string_content("urn:xmpp:mam:2:fin", "http://jabber.org/protocol/rsm" + ":set", "last"); if (!mam_times[account].has_key(latest_id)) error("wtf2"); current_catchup_id[account] = (int) db.mam_catchup.insert() .value(db.mam_catchup.account_id, account.id) .value(db.mam_catchup.from_id, earliest_id) .value(db.mam_catchup.from_time, (long)mam_times[account][earliest_id].to_unix()) .value(db.mam_catchup.to_id, latest_id) .value(db.mam_catchup.to_time, (long)mam_times[account][latest_id].to_unix()) .perform(); } else { // Update existing id db.mam_catchup.update() .set(db.mam_catchup.from_id, earliest_id) .set(db.mam_catchup.from_time, (long)mam_times[account][earliest_id].to_unix()) // need to make sure we have this .with(db.mam_catchup.id, "=", current_catchup_id[account]) .perform(); } TimeSpan catchup_time_ago = (new DateTime.now_utc()).difference(mam_times[account][earliest_id]); int wait_ms = 10; if (catchup_time_ago > 14 * TimeSpan.DAY) { wait_ms = 2000; } else if (catchup_time_ago > 5 * TimeSpan.DAY) { wait_ms = 1000; } else if (catchup_time_ago > 2 * TimeSpan.DAY) { wait_ms = 200; } else if (catchup_time_ago > TimeSpan.DAY) { wait_ms = 50; } mam_times[account] = new HashMap(); Timeout.add(wait_ms, () => { if (hitted_range.has_key(query_id)) { debug(@"MAM: [%s] Hitted contains key %s", account.bare_jid.to_string(), query_id); iq = null; Idle.add(get_mam_range.callback); return false; } stream.get_module(Xep.MessageArchiveManagement.Module.IDENTITY).page_through_results.begin(stream, null, query_id, from_time, to_time, iq, (_, res) => { iq = stream.get_module(Xep.MessageArchiveManagement.Module.IDENTITY).page_through_results.end(res); Idle.add(get_mam_range.callback); }); return false; }); yield; } return false; } private async void on_message_received(Account account, Xmpp.MessageStanza message_stanza) { Entities.Message message = yield parse_message_stanza(account, message_stanza); Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_for_message(message); if (conversation == null) return; // MAM state database update Xep.MessageArchiveManagement.MessageFlag mam_flag = Xep.MessageArchiveManagement.MessageFlag.get_flag(message_stanza); if (mam_flag == null) { if (current_catchup_id.has_key(account)) { string? stanza_id = UniqueStableStanzaIDs.get_stanza_id(message_stanza, account.bare_jid); if (stanza_id != null) { db.mam_catchup.update() .with(db.mam_catchup.id, "=", current_catchup_id[account]) .set(db.mam_catchup.to_time, (long)message.local_time.to_unix()) .set(db.mam_catchup.to_id, stanza_id) .perform(); } } } bool abort = yield received_pipeline.run(message, message_stanza, conversation); if (abort) return; if (message.direction == Entities.Message.DIRECTION_RECEIVED) { message_received(message, conversation); } else if (message.direction == Entities.Message.DIRECTION_SENT) { message_sent(message, conversation); } } public async Entities.Message parse_message_stanza(Account account, Xmpp.MessageStanza message) { Entities.Message new_message = new Entities.Message(message.body); new_message.account = account; new_message.stanza_id = Xep.UniqueStableStanzaIDs.get_origin_id(message) ?? message.id; Jid? counterpart_override = null; if (message.from.equals(stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(message.from.bare_jid, account))) { new_message.direction = Entities.Message.DIRECTION_SENT; counterpart_override = message.from.bare_jid; } else if (account.bare_jid.equals_bare(message.from)) { new_message.direction = Entities.Message.DIRECTION_SENT; } else { new_message.direction = Entities.Message.DIRECTION_RECEIVED; } new_message.counterpart = counterpart_override ?? (new_message.direction == Entities.Message.DIRECTION_SENT ? message.to : message.from); new_message.ourpart = new_message.direction == Entities.Message.DIRECTION_SENT ? message.from : message.to; XmppStream? stream = stream_interactor.get_stream(account); Xep.MessageArchiveManagement.MessageFlag? mam_message_flag = Xep.MessageArchiveManagement.MessageFlag.get_flag(message); Xep.MessageArchiveManagement.Flag? mam_flag = stream != null ? stream.get_flag(Xep.MessageArchiveManagement.Flag.IDENTITY) : null; if (mam_message_flag != null && mam_flag != null && mam_flag.ns_ver == Xep.MessageArchiveManagement.NS_URI && mam_message_flag.mam_id != null) { new_message.server_id = mam_message_flag.mam_id; } else if (message.type_ == Xmpp.MessageStanza.TYPE_GROUPCHAT) { new_message.server_id = Xep.UniqueStableStanzaIDs.get_stanza_id(message, new_message.counterpart.bare_jid); } else if (message.type_ == Xmpp.MessageStanza.TYPE_CHAT) { new_message.server_id = Xep.UniqueStableStanzaIDs.get_stanza_id(message, account.bare_jid); } if (mam_message_flag != null) new_message.local_time = mam_message_flag.server_time; DateTime now = new DateTime.from_unix_utc(new DateTime.now_utc().to_unix()); // Remove milliseconds. They are not stored in the db and might lead to ordering issues when compared with times from the db. if (new_message.local_time == null || new_message.local_time.compare(now) > 0) new_message.local_time = now; Xep.DelayedDelivery.MessageFlag? delayed_message_flag = Xep.DelayedDelivery.MessageFlag.get_flag(message); if (delayed_message_flag != null) new_message.time = delayed_message_flag.datetime; if (new_message.time == null || new_message.time.compare(new_message.local_time) > 0) new_message.time = new_message.local_time; new_message.type_ = yield determine_message_type(account, message, new_message); return new_message; } private async Entities.Message.Type determine_message_type(Account account, Xmpp.MessageStanza message_stanza, Entities.Message message) { if (message_stanza.type_ == Xmpp.MessageStanza.TYPE_GROUPCHAT) { return Entities.Message.Type.GROUPCHAT; } if (message_stanza.type_ == Xmpp.MessageStanza.TYPE_CHAT) { Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(message.counterpart.bare_jid, account); if (conversation != null) { if (conversation.type_ == Conversation.Type.CHAT) { return Entities.Message.Type.CHAT; } else if (conversation.type_ == Conversation.Type.GROUPCHAT) { return Entities.Message.Type.GROUPCHAT_PM; } } else { SourceFunc callback = determine_message_type.callback; XmppStream stream = stream_interactor.get_stream(account); if (stream != null) stream.get_module(Xep.ServiceDiscovery.Module.IDENTITY).get_entity_categories(stream, message.counterpart.bare_jid, (stream, identities) => { if (identities == null) { message.type_ = Entities.Message.Type.CHAT; Idle.add((owned) callback); return; } foreach (Xep.ServiceDiscovery.Identity identity in identities) { if (identity.category == Xep.ServiceDiscovery.Identity.CATEGORY_CONFERENCE) { message.type_ = Entities.Message.Type.GROUPCHAT_PM; } else { message.type_ = Entities.Message.Type.CHAT; } } Idle.add((owned) callback); }); yield; } } return Entities.Message.Type.CHAT; } private class DeduplicateMessageListener : MessageListener { public string[] after_actions_const = new string[]{ "FILTER_EMPTY", "MUC" }; public override string action_group { get { return "DEDUPLICATE"; } } public override string[] after_actions { get { return after_actions_const; } } private MessageProcessor outer; private Database db; public DeduplicateMessageListener(MessageProcessor outer, Database db) { this.outer = outer; this.db = db; } public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) { Account account = conversation.account; Xep.MessageArchiveManagement.MessageFlag? mam_flag = Xep.MessageArchiveManagement.MessageFlag.get_flag(stanza); // Deduplicate by server_id if (message.server_id != null) { QueryBuilder builder = db.message.select() .with(db.message.server_id, "=", message.server_id) .with(db.message.counterpart_id, "=", db.get_jid_id(message.counterpart)) .with(db.message.account_id, "=", account.id); bool duplicate = builder.count() > 0; if (duplicate && mam_flag != null) { debug(@"MAM: [%s] Hitted range duplicate server id. id %s qid %s", account.bare_jid.to_string(), message.server_id, mam_flag.query_id); if (outer.catchup_until_time.has_key(account) && mam_flag.server_time.compare(outer.catchup_until_time[account]) < 0) { outer.hitted_range[mam_flag.query_id] = -1; debug(@"MAM: [%s] In range (time) %s < %s", account.bare_jid.to_string(), mam_flag.server_time.to_string(), outer.catchup_until_time[account].to_string()); } } if (duplicate) return true; } // Deduplicate messages by uuid bool is_uuid = message.stanza_id != null && Regex.match_simple("""[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}""", message.stanza_id); if (is_uuid) { QueryBuilder builder = db.message.select() .with(db.message.stanza_id, "=", message.stanza_id) .with(db.message.counterpart_id, "=", db.get_jid_id(message.counterpart)) .with(db.message.account_id, "=", account.id); if (message.counterpart.resourcepart != null) { builder.with(db.message.counterpart_resource, "=", message.counterpart.resourcepart); } else { builder.with_null(db.message.counterpart_resource); } RowOption row_opt = builder.single().row(); bool duplicate = row_opt.is_present(); if (duplicate && mam_flag != null && row_opt[db.message.server_id] == null && outer.catchup_until_time.has_key(account) && mam_flag.server_time.compare(outer.catchup_until_time[account]) > 0) { outer.hitted_range[mam_flag.query_id] = -1; debug(@"MAM: [%s] Hitted range duplicate message id. id %s qid %s", account.bare_jid.to_string(), message.stanza_id, mam_flag.query_id); } return duplicate; } // Deduplicate messages based on content and metadata QueryBuilder builder = db.message.select() .with(db.message.account_id, "=", account.id) .with(db.message.counterpart_id, "=", db.get_jid_id(message.counterpart)) .with(db.message.body, "=", message.body) .with(db.message.time, "<", (long) message.time.add_minutes(1).to_unix()) .with(db.message.time, ">", (long) message.time.add_minutes(-1).to_unix()); if (message.stanza_id != null) { builder.with(db.message.stanza_id, "=", message.stanza_id); } else { builder.with_null(db.message.stanza_id); } if (message.counterpart.resourcepart != null) { builder.with(db.message.counterpart_resource, "=", message.counterpart.resourcepart); } else { builder.with_null(db.message.counterpart_resource); } return builder.count() > 0; } } private class FilterMessageListener : MessageListener { public string[] after_actions_const = new string[]{ "DECRYPT" }; public override string action_group { get { return "FILTER_EMPTY"; } } public override string[] after_actions { get { return after_actions_const; } } public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) { return (message.body == null); } } private class StoreMessageListener : MessageListener { public string[] after_actions_const = new string[]{ "DEDUPLICATE", "DECRYPT", "FILTER_EMPTY" }; public override string action_group { get { return "STORE"; } } public override string[] after_actions { get { return after_actions_const; } } private StreamInteractor stream_interactor; public StoreMessageListener(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) { if (message.body == null) return true; stream_interactor.get_module(MessageStorage.IDENTITY).add_message(message, conversation); return false; } } private class MamMessageListener : MessageListener { public string[] after_actions_const = new string[]{ "DEDUPLICATE" }; public override string action_group { get { return "MAM_NODE"; } } public override string[] after_actions { get { return after_actions_const; } } private StreamInteractor stream_interactor; public MamMessageListener(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) { bool is_mam_message = Xep.MessageArchiveManagement.MessageFlag.get_flag(stanza) != null; XmppStream? stream = stream_interactor.get_stream(conversation.account); Xep.MessageArchiveManagement.Flag? mam_flag = stream != null ? stream.get_flag(Xep.MessageArchiveManagement.Flag.IDENTITY) : null; if (is_mam_message || (mam_flag != null && mam_flag.cought_up == true)) { conversation.account.mam_earliest_synced = message.local_time; } return false; } } public Entities.Message create_out_message(string text, Conversation conversation) { Entities.Message message = new Entities.Message(text); message.type_ = Util.get_message_type_for_conversation(conversation); message.stanza_id = random_uuid(); message.account = conversation.account; message.body = text; DateTime now = new DateTime.from_unix_utc(new DateTime.now_utc().to_unix()); // Remove milliseconds. They are not stored in the db and might lead to ordering issues when compared with times from the db. message.time = now; message.local_time = now; message.direction = Entities.Message.DIRECTION_SENT; message.counterpart = conversation.counterpart; if (conversation.type_.is_muc_semantic()) { message.ourpart = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account) ?? conversation.account.bare_jid; message.real_jid = conversation.account.bare_jid; } else { message.ourpart = conversation.account.full_jid; } message.marked = Entities.Message.Marked.UNSENT; message.encryption = conversation.encryption; return message; } public void send_xmpp_message(Entities.Message message, Conversation conversation, bool delayed = false) { lock (lock_send_unsent) { XmppStream stream = stream_interactor.get_stream(conversation.account); message.marked = Entities.Message.Marked.NONE; if (stream != null) { Xmpp.MessageStanza new_message = new Xmpp.MessageStanza(message.stanza_id); new_message.to = message.counterpart; new_message.body = message.body; if (conversation.type_ == Conversation.Type.GROUPCHAT) { new_message.type_ = Xmpp.MessageStanza.TYPE_GROUPCHAT; } else { new_message.type_ = Xmpp.MessageStanza.TYPE_CHAT; } build_message_stanza(message, new_message, conversation); pre_message_send(message, new_message, conversation); if (message.marked == Entities.Message.Marked.UNSENT || message.marked == Entities.Message.Marked.WONTSEND) return; if (delayed) { Xmpp.Xep.DelayedDelivery.Module.set_message_delay(new_message, message.time); } // Set an origin ID if a MUC doen't guarantee to keep IDs if (conversation.type_ == Conversation.Type.GROUPCHAT) { Xep.Muc.Flag? flag = stream.get_flag(Xep.Muc.Flag.IDENTITY); if (flag == null) return; if(!flag.has_room_feature(conversation.counterpart, Xep.Muc.Feature.STABLE_ID)) { Xep.UniqueStableStanzaIDs.set_origin_id(new_message, message.stanza_id); } } stream.get_module(Xmpp.MessageModule.IDENTITY).send_message(stream, new_message); } else { message.marked = Entities.Message.Marked.UNSENT; } } } } public abstract class MessageListener : Xmpp.OrderedListener { public abstract async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation); } public class MessageListenerHolder : Xmpp.ListenerHolder { public async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) { foreach (OrderedListener ol in listeners) { MessageListener l = ol as MessageListener; bool stop = yield l.run(message, stanza, conversation); if (stop) return true; } return false; } } } dino-0.1.0/libdino/src/service/message_storage.vala0000644000000000000000000001244713614354364021044 0ustar rootrootusing Gee; using Qlite; using Dino.Entities; namespace Dino { public class MessageStorage : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("message_cache"); public string id { get { return IDENTITY.id; } } private StreamInteractor stream_interactor; private Database db; private HashMap> messages = new HashMap>(Conversation.hash_func, Conversation.equals_func); public static void start(StreamInteractor stream_interactor, Database db) { MessageStorage m = new MessageStorage(stream_interactor, db); stream_interactor.add_module(m); } private MessageStorage(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; this.db = db; } public void add_message(Message message, Conversation conversation) { message.persist(db); init_conversation(conversation); messages[conversation].add(message); stream_interactor.get_module(ContentItemStore.IDENTITY).insert_message(message, conversation); } public Gee.List get_messages(Conversation conversation, int count = 50) { init_conversation(conversation); Gee.List ret = new ArrayList(Message.equals_func); BidirIterator iter = messages[conversation].bidir_iterator(); iter.last(); if (messages[conversation].size > 0) { do { ret.insert(0, iter.get()); iter.previous(); } while (iter.has_previous() && ret.size < count); } return ret; } public Message? get_last_message(Conversation conversation) { init_conversation(conversation); if (messages[conversation].size > 0) { return messages[conversation].last(); } return null; } public Gee.List get_messages_before_message(Conversation? conversation, DateTime before, int id, int count = 20) { // SortedSet? before = messages[conversation].head_set(message); // if (before != null && before.size >= count) { // Gee.List ret = new ArrayList(Message.equals_func); // Iterator iter = before.iterator(); // iter.next(); // for (int from_index = before.size - count; iter.has_next() && from_index > 0; from_index--) iter.next(); // while(iter.has_next()) { // Message m = iter.get(); // ret.add(m); // iter.next(); // } // return ret; // } else { Gee.List db_messages = db.get_messages(conversation.counterpart, conversation.account, Util.get_message_type_for_conversation(conversation), count, before, null, id); Gee.List ret = new ArrayList(); foreach (Message message in db_messages) { ret.add(new MessageItem(message, conversation, -1)); } return ret; // } } public Gee.List get_messages_after_message(Conversation? conversation, DateTime after, int id, int count = 20) { Gee.List db_messages = db.get_messages(conversation.counterpart, conversation.account, Util.get_message_type_for_conversation(conversation), count, null, after, id); Gee.List ret = new ArrayList(); foreach (Message message in db_messages) { ret.add(new MessageItem(message, conversation, -1)); } return ret; } public Message? get_message_by_id(int id, Conversation conversation) { init_conversation(conversation); foreach (Message message in messages[conversation]) { if (message.id == id) return message; } return null; } public Message? get_message_by_stanza_id(string stanza_id, Conversation conversation) { init_conversation(conversation); foreach (Message message in messages[conversation]) { if (message.stanza_id == stanza_id) return message; } return null; } public Conversation? get_conversation_for_stanza_id(Account account, string stanza_id) { foreach (Conversation conversation in messages.keys) { if (!conversation.account.equals(account)) continue; foreach (Message message in messages[conversation]) { if (message.stanza_id == stanza_id) return conversation; } } return null; } private void init_conversation(Conversation conversation) { if (!messages.has_key(conversation)) { messages[conversation] = new Gee.TreeSet((a, b) => { int res = a.local_time.compare(b.local_time); if (res == 0) { res = a.time.compare(b.time); } if (res == 0) { res = a.id - b.id > 0 ? 1 : -1; } return res; }); Gee.List db_messages = db.get_messages(conversation.counterpart, conversation.account, Util.get_message_type_for_conversation(conversation), 50, null, null, -1); messages[conversation].add_all(db_messages); } } } } dino-0.1.0/libdino/src/service/module_manager.vala0000644000000000000000000001066513614354364020653 0ustar rootrootusing Gee; using Dino.Entities; using Xmpp; namespace Dino { public class ModuleManager { private HashMap> module_map = new HashMap>(Account.hash_func, Account.equals_func); private EntityCapabilitiesStorage entity_capabilities_storage; public signal void initialize_account_modules(Account account, ArrayList modules); public ModuleManager(Database db) { entity_capabilities_storage = new EntityCapabilitiesStorage(db); } public T? get_module(Account account, Xmpp.ModuleIdentity identity) { if (identity == null) return null; lock (module_map) { if (!module_map.has_key(account)) { initialize(account); } var res = module_map[account].filter((module) => identity.matches(module)); if (res != null && res.next()) { return identity.cast(res.get()); } } return null; } public ArrayList get_modules(Account account, string? resource = null) { ArrayList modules = new ArrayList(); lock (module_map) { if (!module_map.has_key(account)) initialize(account); foreach (XmppStreamModule module in module_map[account]) modules.add(module); } foreach (XmppStreamModule module in module_map[account]) { if (module.get_id() == Bind.Module.IDENTITY.id) { (module as Bind.Module).requested_resource = resource ?? account.resourcepart; } else if (module.get_id() == Sasl.Module.IDENTITY.id) { (module as Sasl.Module).password = account.password; } } return modules; } public void initialize(Account account) { lock(module_map) { module_map[account] = new ArrayList(); module_map[account].add(new Iq.Module()); module_map[account].add(new Tls.Module()); module_map[account].add(new Xep.SrvRecordsTls.Module()); module_map[account].add(new Sasl.Module(account.bare_jid.to_string(), account.password)); module_map[account].add(new Xep.StreamManagement.Module()); module_map[account].add(new Bind.Module(account.resourcepart)); module_map[account].add(new Session.Module()); module_map[account].add(new Roster.Module()); module_map[account].add(new Xep.ServiceDiscovery.Module.with_identity("client", "pc")); module_map[account].add(new Xep.PrivateXmlStorage.Module()); module_map[account].add(new Xep.Bookmarks.Module()); module_map[account].add(new Xep.Bookmarks2.Module()); module_map[account].add(new Presence.Module()); module_map[account].add(new Xmpp.MessageModule()); module_map[account].add(new Xep.MessageArchiveManagement.Module()); module_map[account].add(new Xep.MessageCarbons.Module()); module_map[account].add(new Xep.Muc.Module()); module_map[account].add(new Xep.Pubsub.Module()); module_map[account].add(new Xep.EntityCapabilities.Module(entity_capabilities_storage)); module_map[account].add(new Xep.MessageDeliveryReceipts.Module()); module_map[account].add(new Xep.BlockingCommand.Module()); module_map[account].add(new Xep.ChatStateNotifications.Module()); module_map[account].add(new Xep.ChatMarkers.Module()); module_map[account].add(new Xep.Ping.Module()); module_map[account].add(new Xep.DelayedDelivery.Module()); module_map[account].add(new StreamError.Module()); module_map[account].add(new Xep.InBandRegistration.Module()); module_map[account].add(new Xep.HttpFileUpload.Module()); module_map[account].add(new Xep.Socks5Bytestreams.Module()); module_map[account].add(new Xep.InBandBytestreams.Module()); module_map[account].add(new Xep.Jingle.Module()); module_map[account].add(new Xep.JingleSocks5Bytestreams.Module()); module_map[account].add(new Xep.JingleInBandBytestreams.Module()); module_map[account].add(new Xep.JingleFileTransfer.Module()); module_map[account].add(new Xep.Jet.Module()); initialize_account_modules(account, module_map[account]); } } } } dino-0.1.0/libdino/src/service/muc_manager.vala0000644000000000000000000005014113614354364020143 0ustar rootrootusing Gee; using Xmpp; using Xmpp.Xep; using Dino.Entities; namespace Dino { public class MucManager : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("muc_manager"); public string id { get { return IDENTITY.id; } } public signal void left(Account account, Jid jid); public signal void subject_set(Account account, Jid jid, string? subject); public signal void room_name_set(Account account, Jid jid, string? room_name); public signal void private_room_occupant_updated(Account account, Jid room, Jid occupant); public signal void invite_received(Account account, Jid room_jid, Jid from_jid, string? password, string? reason); public signal void bookmarks_updated(Account account, Set conferences); public signal void conference_added(Account account, Conference conference); public signal void conference_removed(Account account, Jid jid); private StreamInteractor stream_interactor; private HashMap enter_errors = new HashMap(Jid.hash_func, Jid.equals_func); private ReceivedMessageListener received_message_listener; private HashMap bookmarks_provider = new HashMap(Account.hash_func, Account.equals_func); public static void start(StreamInteractor stream_interactor) { MucManager m = new MucManager(stream_interactor); stream_interactor.add_module(m); } private MucManager(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; this.received_message_listener = new ReceivedMessageListener(stream_interactor); stream_interactor.account_added.connect(on_account_added); stream_interactor.stream_negotiated.connect(on_stream_negotiated); stream_interactor.get_module(MessageProcessor.IDENTITY).received_pipeline.connect(received_message_listener); stream_interactor.get_module(ConversationManager.IDENTITY).conversation_deactivated.connect((conversation) => { if (conversation.type_ == Conversation.Type.GROUPCHAT) { part(conversation.account, conversation.counterpart); } }); } public async Muc.JoinResult? join(Account account, Jid jid, string? nick, string? password) { XmppStream? stream = stream_interactor.get_stream(account); if (stream == null) return null; string nick_ = (nick ?? account.localpart) ?? account.domainpart; DateTime? history_since = null; Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(jid, account); if (conversation != null) { Entities.Message? last_message = stream_interactor.get_module(MessageStorage.IDENTITY).get_last_message(conversation); if (last_message != null) history_since = last_message.time; } Muc.JoinResult? res = yield stream.get_module(Xep.Muc.Module.IDENTITY).enter(stream, jid.bare_jid, nick_, password, history_since); if (res.nick != null) { // Join completed enter_errors.unset(jid); set_autojoin(account, stream, jid, nick, password); stream_interactor.get_module(MessageProcessor.IDENTITY).send_unsent_messages(account, jid); Conversation joined_conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(jid, account, Conversation.Type.GROUPCHAT); joined_conversation.nickname = nick; stream_interactor.get_module(ConversationManager.IDENTITY).start_conversation(conversation); } else if (res.muc_error != null) { // Join failed enter_errors[jid] = res.muc_error; } return res; } public void part(Account account, Jid jid) { XmppStream? stream = stream_interactor.get_stream(account); if (stream == null) return; unset_autojoin(account, stream, jid); stream.get_module(Xep.Muc.Module.IDENTITY).exit(stream, jid.bare_jid); Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(jid, account); if (conversation != null) stream_interactor.get_module(ConversationManager.IDENTITY).close_conversation(conversation); } public async DataForms.DataForm? get_config_form(Account account, Jid jid) { XmppStream? stream = stream_interactor.get_stream(account); if (stream == null) return null; return yield stream.get_module(Xep.Muc.Module.IDENTITY).get_config_form(stream, jid); } public void set_config_form(Account account, Jid jid, DataForms.DataForm data_form) { XmppStream? stream = stream_interactor.get_stream(account); if (stream == null) return; stream.get_module(Xep.Muc.Module.IDENTITY).set_config_form(stream, jid, data_form); } public void change_subject(Account account, Jid jid, string subject) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) stream.get_module(Xep.Muc.Module.IDENTITY).change_subject(stream, jid.bare_jid, subject); } public void change_nick(Account account, Jid jid, string new_nick) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) stream.get_module(Xep.Muc.Module.IDENTITY).change_nick(stream, jid.bare_jid, new_nick); } public void invite(Account account, Jid muc, Jid invitee) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) stream.get_module(Xep.Muc.Module.IDENTITY).invite(stream, muc.bare_jid, invitee.bare_jid); } public void kick(Account account, Jid jid, string nick) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) stream.get_module(Xep.Muc.Module.IDENTITY).kick(stream, jid.bare_jid, nick); } public void change_affiliation(Account account, Jid jid, string nick, string role) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) stream.get_module(Xep.Muc.Module.IDENTITY).change_affiliation(stream, jid.bare_jid, nick, role); } public bool kick_possible(Account account, Jid occupant) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) return stream.get_module(Xep.Muc.Module.IDENTITY).kick_possible(stream, occupant); return false; } //the term `private room` is a short hand for members-only+non-anonymous rooms public bool is_private_room(Account account, Jid jid) { XmppStream? stream = stream_interactor.get_stream(account); if (stream == null) { return false; } Xep.Muc.Flag? flag = stream.get_flag(Xep.Muc.Flag.IDENTITY); if (flag == null) { return false; } return flag.has_room_feature(jid, Xep.Muc.Feature.NON_ANONYMOUS) && flag.has_room_feature(jid, Xep.Muc.Feature.MEMBERS_ONLY); } public bool is_public_room(Account account, Jid jid) { return is_groupchat(jid, account) && !is_private_room(account, jid); } public Gee.List? get_occupants(Jid jid, Account account) { if (is_groupchat(jid, account)) { Gee.List ret = new ArrayList(Jid.equals_func); Gee.List? full_jids = stream_interactor.get_module(PresenceManager.IDENTITY).get_full_jids(jid, account); if (full_jids != null) { ret.add_all(full_jids); // Remove eventual presence from bare jid ret.remove(jid); } return ret; } return null; } public Gee.List? get_other_occupants(Jid jid, Account account) { Gee.List? occupants = get_occupants(jid, account); Jid? own_jid = get_own_jid(jid, account); if (occupants != null && own_jid != null) { occupants.remove(own_jid); } return occupants; } public bool is_groupchat(Jid jid, Account account) { Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(jid, account, Conversation.Type.GROUPCHAT); return !jid.is_full() && conversation != null; } public bool is_groupchat_occupant(Jid jid, Account account) { return is_groupchat(jid.bare_jid, account) && jid.resourcepart != null; } public async Set? get_bookmarks(Account account) { XmppStream? stream = stream_interactor.get_stream(account); if (stream == null) return null; return yield bookmarks_provider[account].get_conferences(stream); } public void add_bookmark(Account account, Conference conference) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) { bookmarks_provider[account].add_conference.begin(stream, conference); } } public void replace_bookmark(Account account, Conference was, Conference replace) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) { stream.get_module(Xep.Bookmarks.Module.IDENTITY).replace_conference.begin(stream, was, replace); } } public void remove_bookmark(Account account, Conference conference) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) { bookmarks_provider[account].remove_conference.begin(stream, conference); } } public string? get_room_name(Account account, Jid jid) { Xep.Muc.Flag? flag = get_muc_flag(account); if (flag != null) { return flag.get_room_name(jid); } return null; } public string? get_groupchat_subject(Jid jid, Account account) { Xep.Muc.Flag? flag = get_muc_flag(account); if (flag != null) { return flag.get_muc_subject(jid.bare_jid); } return null; } public Jid? get_real_jid(Jid jid, Account account) { Xep.Muc.Flag? flag = get_muc_flag(account); if (flag != null) { return flag.get_real_jid(jid); } return null; } public Xep.Muc.Role? get_role(Jid jid, Account account) { Xep.Muc.Flag? flag = get_muc_flag(account); if (flag != null) { return flag.get_occupant_role(jid); } return null; } public Xep.Muc.Affiliation? get_affiliation(Jid muc_jid, Jid jid, Account account) { Xep.Muc.Flag? flag = get_muc_flag(account); if (flag != null) { return flag.get_affiliation(muc_jid, jid); } return null; } public Gee.List? get_offline_members(Jid jid, Account account) { Xep.Muc.Flag? flag = get_muc_flag(account); if (flag != null) { return flag.get_offline_members(jid); } return null; } public Gee.List? get_other_offline_members(Jid jid, Account account) { Gee.List? occupants = get_offline_members(jid, account); if (occupants != null) { occupants.remove(account.bare_jid); } return occupants; } public Jid? get_own_jid(Jid muc_jid, Account account) { try { Xep.Muc.Flag? flag = get_muc_flag(account); if (flag != null) { string? nick = flag.get_muc_nick(muc_jid); if (nick != null) return muc_jid.with_resource(nick); } } catch (InvalidJidError e) { warning("Joined MUC with invalid Jid: %s", e.message); } return null; } private Xep.Muc.Flag? get_muc_flag(Account account) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) { return stream.get_flag(Xep.Muc.Flag.IDENTITY); } return null; } public bool is_joined(Jid jid, Account account) { return get_own_jid(jid, account) != null; } private void on_account_added(Account account) { stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).self_removed_from_room.connect( (stream, jid, code) => { left(account, jid); }); stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).subject_set.connect( (stream, subject, jid) => { subject_set(account, jid, subject); }); stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).invite_received.connect( (stream, room_jid, from_jid, password, reason) => { invite_received(account, room_jid, from_jid, password, reason); }); stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).room_name_set.connect( (stream, jid, room_name) => { room_name_set(account, jid, room_name); }); stream_interactor.module_manager.get_module(account, Xep.Muc.Module.IDENTITY).received_occupant_jid.connect( (stream, room, occupant) => { if (is_private_room(account, room.bare_jid)) { private_room_occupant_updated(account, room, occupant); } }); bookmarks_provider[account] = stream_interactor.module_manager.get_module(account, Xep.Bookmarks.Module.IDENTITY); bookmarks_provider[account].received_conferences.connect( (stream, conferences) => { sync_autojoin_active(account, conferences); bookmarks_updated(account, conferences); }); bookmarks_provider[account].conference_added.connect( (stream, conference) => { // TODO join (for Bookmarks2) conference_added(account, conference); }); bookmarks_provider[account].conference_removed.connect( (stream, jid) => { // TODO part (for Bookmarks2) conference_removed(account, jid); }); } private async void on_stream_negotiated(Account account, XmppStream stream) { if (bookmarks_provider[account] == null) return; Set? conferences = yield bookmarks_provider[account].get_conferences(stream); if (conferences == null) { join_all_active(account); } else { sync_autojoin_active(account, conferences); } } private void join_all_active(Account account) { Gee.List conversations = stream_interactor.get_module(ConversationManager.IDENTITY).get_active_conversations(account); foreach (Conversation conversation in conversations) { if (conversation.type_ == Conversation.Type.GROUPCHAT && conversation.nickname != null) { join.begin(account, conversation.counterpart, conversation.nickname, null); } } } private void sync_autojoin_active(Account account, Set conferences) { Gee.List active_conversations = stream_interactor.get_module(ConversationManager.IDENTITY).get_active_conversations(account); // Join auto-join MUCs foreach (Conference conference in conferences) { if (!conference.autojoin) continue; bool is_active = false; foreach (Conversation conversation in active_conversations) { if (conference.jid.equals(conversation.counterpart)) { is_active = true; } } if (!is_active || !is_joined(conference.jid, account)) { join.begin(account, conference.jid, conference.nick, conference.password); } } // Part MUCs that aren't auto-join (which closes those conversations) foreach (Conversation conversation in active_conversations) { if (conversation.type_ != Conversation.Type.GROUPCHAT) continue; bool should_be_active = false; foreach (Conference conference in conferences) { if (conference.jid.equals(conversation.counterpart) && conference.autojoin) { should_be_active = true; } } if (!should_be_active) { part(conversation.account, conversation.counterpart); } } } private void set_autojoin(Account account, XmppStream stream, Jid jid, string? nick, string? password) { bookmarks_provider[account].get_conferences.begin(stream, (_, res) => { Set? conferences = bookmarks_provider[account].get_conferences.end(res); if (conferences == null) return; foreach (Conference conference in conferences) { if (conference.jid.equals(jid)) { if (!conference.autojoin) { Conference new_conference = new Conference(); new_conference.jid = jid; new_conference.autojoin = true; new_conference.nick = nick; new_conference.password = password; bookmarks_provider[account].replace_conference.begin(stream, conference, new_conference); } return; } } Conference changed = new Xep.Bookmarks.Bookmarks1Conference(jid) { nick=nick, password=password, autojoin=true }; bookmarks_provider[account].add_conference.begin(stream, changed); }); } private void unset_autojoin(Account account, XmppStream stream, Jid jid) { bookmarks_provider[account].get_conferences.begin(stream, (_, res) => { Set? conferences = bookmarks_provider[account].get_conferences.end(res); if (conferences == null) return; foreach (Conference conference in conferences) { if (conference.jid.equals(jid)) { if (conference.autojoin) { Conference new_conference = new Conference(); new_conference.jid = jid; new_conference.autojoin = false; new_conference.nick = conference.nick; new_conference.password = conference.password; bookmarks_provider[account].replace_conference.begin(stream, conference, new_conference); return; } } } }); } private class ReceivedMessageListener : MessageListener { public string[] after_actions_const = new string[]{ }; public override string action_group { get { return "MUC"; } } public override string[] after_actions { get { return after_actions_const; } } private StreamInteractor stream_interactor; public ReceivedMessageListener(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) { if (conversation.type_ != Conversation.Type.GROUPCHAT) return false; XmppStream stream = stream_interactor.get_stream(conversation.account); if (stream == null) return false; if (Xep.DelayedDelivery.MessageFlag.get_flag(stanza) == null) { Jid? real_jid = stream.get_flag(Xep.Muc.Flag.IDENTITY).get_real_jid(message.counterpart); if (real_jid != null && !real_jid.equals(message.counterpart)) { message.real_jid = real_jid.bare_jid; } } Jid? own_muc_jid = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(message.counterpart.bare_jid, conversation.account); if (stanza.id != null && own_muc_jid != null && message.from.equals(own_muc_jid)) { Entities.Message? m = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_stanza_id(stanza.id, conversation); if (m != null) { // For own messages from this device (msg is a duplicate) m.marked = Message.Marked.RECEIVED; } // For own messages from other devices (msg is not a duplicate msg) message.marked = Message.Marked.RECEIVED; } return false; } } } } dino-0.1.0/libdino/src/service/notification_events.vala0000644000000000000000000001022513614354364021736 0ustar rootrootusing Gee; using Dino.Entities; using Xmpp; namespace Dino { public class NotificationEvents : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("notification_events"); public string id { get { return IDENTITY.id; } } public signal void notify_content_item(ContentItem content_item, Conversation conversation); public signal void notify_subscription_request(Conversation conversation); public signal void notify_connection_error(Account account, ConnectionManager.ConnectionError error); public signal void notify_muc_invite(Account account, Jid room_jid, Jid from_jid, string? password, string? reason); private StreamInteractor stream_interactor; public static void start(StreamInteractor stream_interactor) { NotificationEvents m = new NotificationEvents(stream_interactor); stream_interactor.add_module(m); } public NotificationEvents(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; stream_interactor.get_module(ContentItemStore.IDENTITY).new_item.connect(on_content_item_received); stream_interactor.get_module(PresenceManager.IDENTITY).received_subscription_request.connect(on_received_subscription_request); stream_interactor.get_module(MucManager.IDENTITY).invite_received.connect((account, room_jid, from_jid, password, reason) => notify_muc_invite(account, room_jid, from_jid, password, reason)); stream_interactor.connection_manager.connection_error.connect((account, error) => notify_connection_error(account, error)); } private void on_content_item_received(ContentItem item, Conversation conversation) { ContentItem last_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_latest(conversation); bool not_read_up_to = true; MessageItem message_item = item as MessageItem; if (message_item != null) { not_read_up_to = conversation.read_up_to != null && !conversation.read_up_to.equals(message_item.message); } if (item.id != last_item.id && not_read_up_to) return; if (!should_notify(item, conversation)) return; if (stream_interactor.get_module(ChatInteraction.IDENTITY).is_active_focus()) return; notify_content_item(item, conversation); } private bool should_notify(ContentItem content_item, Conversation conversation) { Conversation.NotifySetting notify = conversation.get_notification_setting(stream_interactor); switch (content_item.type_) { case MessageItem.TYPE: Message message = (content_item as MessageItem).message; if (message.direction == Message.DIRECTION_SENT) return false; break; case FileItem.TYPE: FileTransfer file_transfer = (content_item as FileItem).file_transfer; // Don't notify on file transfers in a groupchat set to "mention only" if (notify == Conversation.NotifySetting.HIGHLIGHT) return false; if (file_transfer.direction == FileTransfer.DIRECTION_SENT) return false; break; } if (notify == Conversation.NotifySetting.OFF) return false; Jid? nick = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account); if (content_item.type_ == MessageItem.TYPE) { Entities.Message message = (content_item as MessageItem).message; if (notify == Conversation.NotifySetting.HIGHLIGHT && nick != null) { return Regex.match_simple("\\b" + Regex.escape_string(nick.resourcepart) + "\\b", message.body, RegexCompileFlags.CASELESS); } } return true; } private void on_received_subscription_request(Jid jid, Account account) { Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(jid, account, Conversation.Type.CHAT); if (stream_interactor.get_module(ChatInteraction.IDENTITY).is_active_focus(conversation)) return; notify_subscription_request(conversation); } } } dino-0.1.0/libdino/src/service/presence_manager.vala0000644000000000000000000001455213614354364021171 0ustar rootrootusing Gee; using Xmpp; using Dino.Entities; namespace Dino { public class PresenceManager : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("presence_manager"); public string id { get { return IDENTITY.id; } } public signal void show_received(Show show, Jid jid, Account account); public signal void received_subscription_request(Jid jid, Account account); public signal void received_subscription_approval(Jid jid, Account account); private StreamInteractor stream_interactor; private HashMap>> shows = new HashMap>>(Jid.hash_bare_func, Jid.equals_bare_func); private HashMap> resources = new HashMap>(Jid.hash_bare_func, Jid.equals_bare_func); private Gee.List subscription_requests = new ArrayList(Jid.equals_func); public static void start(StreamInteractor stream_interactor) { PresenceManager m = new PresenceManager(stream_interactor); stream_interactor.add_module(m); } private PresenceManager(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; stream_interactor.account_added.connect(on_account_added); } public Show get_last_show(Jid jid, Account account) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) { Xmpp.Presence.Stanza? presence = stream.get_flag(Presence.Flag.IDENTITY).get_presence(jid); if (presence != null) { return new Show(jid, presence.show, new DateTime.now_utc()); } } return new Show(jid, Show.OFFLINE, new DateTime.now_utc()); } public HashMap>? get_shows(Jid jid, Account account) { return shows[jid]; } public Gee.List? get_full_jids(Jid jid, Account account) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) { Xmpp.Presence.Flag flag = stream.get_flag(Presence.Flag.IDENTITY); if (flag == null) return null; return flag.get_resources(jid.bare_jid); } return null; } public bool exists_subscription_request(Account account, Jid jid) { return subscription_requests.contains(jid); } public void request_subscription(Account account, Jid jid) { XmppStream stream = stream_interactor.get_stream(account); if (stream != null) stream.get_module(Xmpp.Presence.Module.IDENTITY).request_subscription(stream, jid.bare_jid); } public void approve_subscription(Account account, Jid jid) { XmppStream stream = stream_interactor.get_stream(account); if (stream != null) { stream.get_module(Xmpp.Presence.Module.IDENTITY).approve_subscription(stream, jid.bare_jid); subscription_requests.remove(jid); } } public void deny_subscription(Account account, Jid jid) { XmppStream stream = stream_interactor.get_stream(account); if (stream != null) { stream.get_module(Xmpp.Presence.Module.IDENTITY).deny_subscription(stream, jid.bare_jid); subscription_requests.remove(jid); } } public void cancel_subscription(Account account, Jid jid) { XmppStream stream = stream_interactor.get_stream(account); if (stream != null) stream.get_module(Xmpp.Presence.Module.IDENTITY).cancel_subscription(stream, jid.bare_jid); } private void on_account_added(Account account) { stream_interactor.module_manager.get_module(account, Presence.Module.IDENTITY).received_available_show.connect((stream, jid, show) => on_received_available_show(account, jid, show) ); stream_interactor.module_manager.get_module(account, Presence.Module.IDENTITY).received_unavailable.connect((stream, presence) => on_received_unavailable(account, presence.from) ); stream_interactor.module_manager.get_module(account, Presence.Module.IDENTITY).received_subscription_request.connect((stream, jid) => { if (!subscription_requests.contains(jid)) { subscription_requests.add(jid); } received_subscription_request(jid, account); }); stream_interactor.module_manager.get_module(account, Presence.Module.IDENTITY).received_subscription_approval.connect((stream, jid) => { received_subscription_approval(jid, account); }); } private void on_received_available_show(Account account, Jid jid, string show) { lock (resources) { if (!resources.has_key(jid)){ resources[jid] = new ArrayList(Jid.equals_func); } if (!resources[jid].contains(jid)) { resources[jid].add(jid); } } add_show(account, jid, show); } private void on_received_unavailable(Account account, Jid jid) { lock (resources) { if (resources.has_key(jid)) { resources[jid].remove(jid); if (resources[jid].size == 0 || jid.is_bare()) { resources.unset(jid); } } } add_show(account, jid, Show.OFFLINE); } private void add_show(Account account, Jid jid, string s) { Show show = new Show(jid, s, new DateTime.now_utc()); lock (shows) { if (!shows.has_key(jid)) { shows[jid] = new HashMap>(); } if (!shows[jid].has_key(jid)) { shows[jid][jid] = new ArrayList(); } shows[jid][jid].add(show); } show_received(show, jid, account); } } public class Show : Object { public const string ONLINE = Xmpp.Presence.Stanza.SHOW_ONLINE; public const string AWAY = Xmpp.Presence.Stanza.SHOW_AWAY; public const string CHAT = Xmpp.Presence.Stanza.SHOW_CHAT; public const string DND = Xmpp.Presence.Stanza.SHOW_DND; public const string XA = Xmpp.Presence.Stanza.SHOW_XA; public const string OFFLINE = "offline"; public Jid jid; public string as; public DateTime datetime; public Show(Jid jid, string show, DateTime datetime) { this.jid = jid; this.as = show; this.datetime = datetime; } } } dino-0.1.0/libdino/src/service/registration.vala0000644000000000000000000001501413614354364020377 0ustar rootrootusing Gee; using Xmpp; using Dino.Entities; namespace Dino { public class Register : StreamInteractionModule, Object{ public static ModuleIdentity IDENTITY = new ModuleIdentity("registration"); public string id { get { return IDENTITY.id; } } private StreamInteractor stream_interactor; private Database db; public static void start(StreamInteractor stream_interactor, Database db) { Register m = new Register(stream_interactor, db); stream_interactor.add_module(m); } private Register(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; this.db = db; } public async ConnectionManager.ConnectionError.Source? add_check_account(Account account) { XmppStream stream = new XmppStream(); stream.log = new XmppLog(account.bare_jid.to_string(), Application.print_xmpp); stream.add_module(new Tls.Module()); stream.add_module(new Iq.Module()); stream.add_module(new Xep.SrvRecordsTls.Module()); stream.add_module(new Sasl.Module(account.bare_jid.to_string(), account.password)); ConnectionManager.ConnectionError.Source? ret = null; SourceFunc callback = add_check_account.callback; stream.stream_negotiated.connect(() => { if (callback == null) return; Idle.add((owned)callback); }); stream.get_module(Tls.Module.IDENTITY).invalid_certificate.connect((peer_cert, errors) => { if (callback == null) return; ret = ConnectionManager.ConnectionError.Source.TLS; Idle.add((owned)callback); }); stream.get_module(Sasl.Module.IDENTITY).received_auth_failure.connect((stream, node) => { if (callback == null) return; ret = ConnectionManager.ConnectionError.Source.SASL; Idle.add((owned)callback); }); stream.connect.begin(account.domainpart, (_, res) => { try { stream.connect.end(res); } catch (Error e) { debug("Error connecting to stream: %s", e.message); } if (callback != null) { ret = ConnectionManager.ConnectionError.Source.CONNECTION; Idle.add((owned)callback); } }); yield; try { yield stream.disconnect(); } catch (Error e) {} return ret; } public class ServerAvailabilityReturn { public bool available { get; set; } public TlsCertificateFlags? error_flags { get; set; } } public static async ServerAvailabilityReturn check_server_availability(Jid jid) { XmppStream stream = new XmppStream(); stream.log = new XmppLog(jid.to_string(), Application.print_xmpp); stream.add_module(new Tls.Module()); stream.add_module(new Iq.Module()); stream.add_module(new Xep.SrvRecordsTls.Module()); ServerAvailabilityReturn ret = new ServerAvailabilityReturn() { available=false }; SourceFunc callback = check_server_availability.callback; stream.stream_negotiated.connect(() => { if (callback != null) { ret.available = true; Idle.add((owned)callback); } }); stream.get_module(Tls.Module.IDENTITY).invalid_certificate.connect((peer_cert, errors) => { if (callback != null) { ret.error_flags = errors; Idle.add((owned)callback); } }); stream.connect.begin(jid.domainpart, (_, res) => { try { stream.connect.end(res); } catch (Error e) { debug("Error connecting to stream: %s", e.message); } if (callback != null) { Idle.add((owned)callback); } }); yield; try { yield stream.disconnect(); } catch (Error e) {} return ret; } public static async Xep.InBandRegistration.Form? get_registration_form(Jid jid) { XmppStream stream = new XmppStream(); stream.log = new XmppLog(jid.to_string(), Application.print_xmpp); stream.add_module(new Tls.Module()); stream.add_module(new Iq.Module()); stream.add_module(new Xep.SrvRecordsTls.Module()); stream.add_module(new Xep.InBandRegistration.Module()); SourceFunc callback = get_registration_form.callback; stream.stream_negotiated.connect(() => { if (callback != null) { Idle.add((owned)callback); } }); stream.connect.begin(jid.domainpart, (_, res) => { try { stream.connect.end(res); } catch (Error e) { debug("Error connecting to stream: %s", e.message); } if (callback != null) { Idle.add((owned)callback); } }); yield; Xep.InBandRegistration.Form? form = null; if (stream.negotiation_complete) { form = yield stream.get_module(Xep.InBandRegistration.Module.IDENTITY).get_from_server(stream, jid); } try { yield stream.disconnect(); } catch (Error e) {} return form; } public static async string? submit_form(Jid jid, Xep.InBandRegistration.Form form) { XmppStream stream = new XmppStream(); stream.log = new XmppLog(jid.to_string(), Application.print_xmpp); stream.add_module(new Tls.Module()); stream.add_module(new Iq.Module()); stream.add_module(new Xep.SrvRecordsTls.Module()); stream.add_module(new Xep.InBandRegistration.Module()); SourceFunc callback = submit_form.callback; stream.stream_negotiated.connect(() => { if (callback != null) { Idle.add((owned)callback); } }); stream.connect.begin(jid.domainpart, (_, res) => { try { stream.connect.end(res); } catch (Error e) { debug("Error connecting to stream: %s", e.message); } if (callback != null) { Idle.add((owned)callback); } }); yield; string? ret = null; if (stream.negotiation_complete) { ret = yield stream.get_module(Xep.InBandRegistration.Module.IDENTITY).submit_to_server(stream, jid, form); } try { yield stream.disconnect(); } catch (Error e) {} return ret; } } } dino-0.1.0/libdino/src/service/roster_manager.vala0000644000000000000000000001341413614354364020677 0ustar rootrootusing Gee; using Xmpp; using Dino.Entities; namespace Dino { public class RosterManager : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("roster_manager"); public string id { get { return IDENTITY.id; } } public signal void removed_roster_item(Account account, Jid jid, Roster.Item roster_item); public signal void updated_roster_item(Account account, Jid jid, Roster.Item roster_item); public signal void mutual_subscription(Account account, Jid jid); private StreamInteractor stream_interactor; private Database db; private Gee.Map roster_stores = new HashMap(Account.hash_func, Account.equals_func); public static void start(StreamInteractor stream_interactor, Database db) { RosterManager m = new RosterManager(stream_interactor, db); stream_interactor.add_module(m); } public RosterManager(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; this.db = db; stream_interactor.account_added.connect(on_account_added); stream_interactor.module_manager.initialize_account_modules.connect((account, modules) => { if (!roster_stores.has_key(account)) roster_stores[account] = new RosterStoreImpl(account, db); modules.add(new Roster.VersioningModule(roster_stores[account])); }); } public Collection get_roster(Account account) { if (roster_stores[account] == null) return new ArrayList(); return roster_stores[account].get_roster(); } public Roster.Item? get_roster_item(Account account, Jid jid) { if (roster_stores[account] == null) return null; return roster_stores[account].get_item(jid); } public void remove_jid(Account account, Jid jid) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) stream.get_module(Xmpp.Roster.Module.IDENTITY).remove_jid(stream, jid.bare_jid); } public void add_jid(Account account, Jid jid, string? handle) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) stream.get_module(Xmpp.Roster.Module.IDENTITY).add_jid(stream, jid.bare_jid, handle); } public void set_jid_handle(Account account, Jid jid, string? handle) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) stream.get_module(Xmpp.Roster.Module.IDENTITY).set_jid_handle(stream, jid.bare_jid, handle); } private void on_account_added(Account account) { stream_interactor.module_manager.get_module(account, Roster.Module.IDENTITY).received_roster.connect_after( (stream, roster) => { foreach (Roster.Item roster_item in roster) { on_roster_item_updated(account, roster_item); } }); stream_interactor.module_manager.get_module(account, Roster.Module.IDENTITY).item_removed.connect_after( (stream, roster_item) => { removed_roster_item(account, roster_item.jid, roster_item); }); stream_interactor.module_manager.get_module(account, Roster.Module.IDENTITY).item_updated.connect_after( (stream, roster_item) => { on_roster_item_updated(account, roster_item); }); stream_interactor.module_manager.get_module(account, Roster.Module.IDENTITY).mutual_subscription.connect_after( (stream, jid) => { mutual_subscription(account, jid); }); } private void on_roster_item_updated(Account account, Roster.Item roster_item) { updated_roster_item(account, roster_item.jid, roster_item); } } public class RosterStoreImpl : Roster.Storage, Object { private Account account; private Database db; private HashMap items = new HashMap(Jid.hash_bare_func, Jid.equals_bare_func); public class RosterStoreImpl(Account account, Database db) { this.account = account; this.db = db; foreach (Qlite.Row row in db.roster.select().with(db.roster.account_id, "=", account.id)) { try { Roster.Item item = new Roster.Item(); item.jid = new Jid(row[db.roster.jid]); item.name = row[db.roster.handle]; item.subscription = row[db.roster.subscription]; items[item.jid] = item; } catch (InvalidJidError e) { warning("Ignoring roster entry with invalid Jid: %s", e.message); } } } public string? get_roster_version() { return account.roster_version; } public Collection get_roster() { return items.values; } public Roster.Item? get_item(Jid jid) { return items.has_key(jid) ? items[jid] : null; } public void set_roster_version(string version) { account.roster_version = version; } public void set_roster(Collection items) { db.roster.delete().with(db.roster.account_id, "=", account.id).perform(); foreach (Roster.Item item in items) { set_item(item); } } public void set_item(Roster.Item item) { items[item.jid] = item; db.roster.insert().or("REPLACE") .value(db.roster.account_id, account.id) .value(db.roster.jid, item.jid.to_string()) .value(db.roster.handle, item.name) .value(db.roster.subscription, item.subscription) .perform(); } public void remove_item(Roster.Item item) { items.unset(item.jid); db.roster.delete() .with(db.roster.account_id, "=", account.id) .with(db.roster.jid, "=", item.jid.to_string()).perform(); } } } dino-0.1.0/libdino/src/service/search_processor.vala0000644000000000000000000003553413614354364021242 0ustar rootrootusing Gee; using Xmpp; using Qlite; using Dino.Entities; namespace Dino { public class SearchProcessor : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("search_processor"); public string id { get { return IDENTITY.id; } } private StreamInteractor stream_interactor; private Database db; public static void start(StreamInteractor stream_interactor, Database db) { SearchProcessor m = new SearchProcessor(stream_interactor, db); stream_interactor.add_module(m); } public SearchProcessor(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; this.db = db; } private QueryBuilder prepare_search(string query, bool join_content) { string words = ""; string? with = null; string? in_ = null; string? from = null; foreach(string word in query.split(" ")) { if (word.has_prefix("with:")) { if (with == null) { with = word.substring(5); } else { return db.message.select().where("0"); } } else if (word.has_prefix("in:")) { if (in_ == null) { in_ = word.substring(3); } else { return db.message.select().where("0"); } } else if (word.has_prefix("from:")) { if (from == null) { from = word.substring(5); } else { return db.message.select().where("0"); } } else { words += word + "* "; } } if (in_ != null && with != null) { return db.message.select().where("0"); } QueryBuilder rows = db.message .match(db.message.body, words) .order_by(db.message.id, "DESC") .join_with(db.jid, db.jid.id, db.message.counterpart_id) .join_with(db.account, db.account.id, db.message.account_id) .outer_join_with(db.real_jid, db.real_jid.message_id, db.message.id) .with(db.account.enabled, "=", true); if (join_content) { rows.join_on(db.content_item, "message.id=content_item.foreign_id AND content_item.content_type=1") .with(db.content_item.content_type, "=", 1); } if (with != null) { if (with.index_of("/") > 0) { rows.with(db.message.type_, "=", Message.Type.GROUPCHAT_PM) .with(db.jid.bare_jid, "LIKE", with.substring(0, with.index_of("/"))) .with(db.message.counterpart_resource, "LIKE", with.substring(with.index_of("/") + 1)); } else { rows.where(@"($(db.message.type_) = $((int)Message.Type.CHAT) AND $(db.jid.bare_jid) LIKE ?)" + @" OR ($(db.message.type_) = $((int)Message.Type.GROUPCHAT_PM) AND $(db.real_jid.real_jid) LIKE ?)" + @" OR ($(db.message.type_) = $((int)Message.Type.GROUPCHAT_PM) AND $(db.message.counterpart_resource) LIKE ?)", {with, with, with}); } } else if (in_ != null) { rows.with(db.jid.bare_jid, "LIKE", in_) .with(db.message.type_, "=", Message.Type.GROUPCHAT); } if (from != null) { rows.where(@"($(db.message.direction) = 1 AND $(db.account.bare_jid) LIKE ?)" + @" OR ($(db.message.direction) = 1 AND $(db.message.type_) IN ($((int)Message.Type.GROUPCHAT), $((int)Message.Type.GROUPCHAT_PM)) AND $(db.message.our_resource) LIKE ?)" + @" OR ($(db.message.direction) = 0 AND $(db.message.type_) = $((int)Message.Type.CHAT) AND $(db.jid.bare_jid) LIKE ?)" + @" OR ($(db.message.direction) = 0 AND $(db.message.type_) IN ($((int)Message.Type.GROUPCHAT), $((int)Message.Type.GROUPCHAT_PM)) AND $(db.real_jid.real_jid) LIKE ?)" + @" OR ($(db.message.direction) = 0 AND $(db.message.type_) IN ($((int)Message.Type.GROUPCHAT), $((int)Message.Type.GROUPCHAT_PM)) AND $(db.message.counterpart_resource) LIKE ?)", {from, from, from, from, from}); } return rows; } public Gee.List suggest_auto_complete(string query, int cursor_position, int limit = 5) { int after_prev_space = query.substring(0, cursor_position).last_index_of(" ") + 1; int next_space = query.index_of(" ", after_prev_space); if (next_space < 0) next_space = query.length; string current_query = query.substring(after_prev_space, next_space - after_prev_space); Gee.List suggestions = new ArrayList(); if (current_query.has_prefix("from:")) { if (cursor_position < after_prev_space + 5) return suggestions; string current_from = current_query.substring(5); string[] splitted = query.split(" "); foreach(string s in splitted) { if (s.has_prefix("from:") && s != "from:" + current_from) { // Already have an from: filter -> no useful autocompletion possible return suggestions; } } string? current_in = null; string? current_with = null; foreach(string s in splitted) { if (s.has_prefix("in:")) { current_in = s.substring(3); } else if (s.has_prefix("with:")) { current_with = s.substring(5); } } if (current_in != null && current_with != null) { // in: and with: -> no useful autocompletion possible return suggestions; } if (current_with != null) { // Can only be the other one or us // Normal chat QueryBuilder chats = db.conversation.select() .join_with(db.jid, db.jid.id, db.conversation.jid_id) .join_with(db.account, db.account.id, db.conversation.account_id) .with(db.jid.bare_jid, "=", current_with) .with(db.account.enabled, "=", true) .with(db.conversation.type_, "=", Conversation.Type.CHAT) .order_by(db.conversation.last_active, "DESC"); foreach(Row chat in chats) { try { if (suggestions.size == 0) { suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), new Jid(chat[db.jid.bare_jid]), "from:"+chat[db.jid.bare_jid], after_prev_space, next_space)); } suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), new Jid(chat[db.account.bare_jid]), "from:"+chat[db.account.bare_jid], after_prev_space, next_space)); } catch (InvalidJidError e) { warning("Ignoring search suggestion with invalid Jid: %s", e.message); } } return suggestions; } if (current_in != null) { // All members of the MUC with history QueryBuilder msgs = db.message.select() .select_string(@"account.*, $(db.message.counterpart_resource), conversation.*") .join_with(db.jid, db.jid.id, db.message.counterpart_id) .join_with(db.account, db.account.id, db.message.account_id) .join_on(db.conversation, @"$(db.conversation.account_id)=$(db.account.id) AND $(db.conversation.jid_id)=$(db.jid.id)") .with(db.jid.bare_jid, "=", current_in) .with(db.account.enabled, "=", true) .with(db.message.type_, "=", Message.Type.GROUPCHAT) .with(db.conversation.type_, "=", Conversation.Type.GROUPCHAT) .with(db.message.counterpart_resource, "LIKE", @"%$current_from%") .group_by({db.message.counterpart_resource}) .order_by_name(@"MAX($(db.message.time))", "DESC") .limit(5); foreach(Row msg in msgs) { try { suggestions.add(new SearchSuggestion(new Conversation.from_row(db, msg), new Jid(current_in).with_resource(msg[db.message.counterpart_resource]), "from:"+msg[db.message.counterpart_resource], after_prev_space, next_space)); } catch (InvalidJidError e) { warning("Ignoring search suggestion with invalid Jid: %s", e.message); } } } // TODO: auto complete from } else if (current_query.has_prefix("with:")) { if (cursor_position < after_prev_space + 5) return suggestions; string current_with = current_query.substring(5); string[] splitted = query.split(" "); foreach(string s in splitted) { if ((s.has_prefix("with:") && s != "with:" + current_with) || s.has_prefix("in:")) { // Already have an in: or with: filter -> no useful autocompletion possible return suggestions; } } // Normal chat QueryBuilder chats = db.conversation.select() .join_with(db.jid, db.jid.id, db.conversation.jid_id) .join_with(db.account, db.account.id, db.conversation.account_id) .outer_join_on(db.roster, @"$(db.jid.bare_jid) = $(db.roster.jid) AND $(db.account.id) = $(db.roster.account_id)") .where(@"$(db.jid.bare_jid) LIKE ? OR $(db.roster.handle) LIKE ?", {@"%$current_with%", @"%$current_with%"}) .with(db.account.enabled, "=", true) .with(db.conversation.type_, "=", Conversation.Type.CHAT) .order_by(db.conversation.last_active, "DESC") .limit(limit); foreach(Row chat in chats) { try { suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), new Jid(chat[db.jid.bare_jid]), "with:"+chat[db.jid.bare_jid], after_prev_space, next_space) { order = chat[db.conversation.last_active]}); } catch (InvalidJidError e) { warning("Ignoring search suggestion with invalid Jid: %s", e.message); } } // Groupchat PM if (suggestions.size < 5) { chats = db.conversation.select() .join_with(db.jid, db.jid.id, db.conversation.jid_id) .join_with(db.account, db.account.id, db.conversation.account_id) .where(@"$(db.jid.bare_jid) LIKE ? OR $(db.conversation.resource) LIKE ?", {@"%$current_with%", @"%$current_with%"}) .with(db.account.enabled, "=", true) .with(db.conversation.type_, "=", Conversation.Type.GROUPCHAT_PM) .order_by(db.conversation.last_active, "DESC") .limit(limit - suggestions.size); foreach(Row chat in chats) { try { suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), new Jid(chat[db.jid.bare_jid]).with_resource(chat[db.conversation.resource]), "with:"+chat[db.jid.bare_jid]+"/"+chat[db.conversation.resource], after_prev_space, next_space) { order = chat[db.conversation.last_active]}); } catch (InvalidJidError e) { warning("Ignoring search suggestion with invalid Jid: %s", e.message); } } suggestions.sort((a, b) => (int)(b.order - a.order)); } } else if (current_query.has_prefix("in:")) { if (cursor_position < after_prev_space + 3) return suggestions; string current_in = current_query.substring(3); string[] splitted = query.split(" "); foreach(string s in splitted) { if ((s.has_prefix("in:") && s != "in:" + current_in) || s.has_prefix("with:")) { // Already have an in: or with: filter -> no useful autocompletion possible return suggestions; } } QueryBuilder groupchats = db.conversation.select() .join_with(db.jid, db.jid.id, db.conversation.jid_id) .join_with(db.account, db.account.id, db.conversation.account_id) .with(db.jid.bare_jid, "LIKE", @"%$current_in%") .with(db.account.enabled, "=", true) .with(db.conversation.type_, "=", Conversation.Type.GROUPCHAT) .order_by(db.conversation.last_active, "DESC") .limit(limit); foreach(Row chat in groupchats) { try { suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), new Jid(chat[db.jid.bare_jid]), "in:"+chat[db.jid.bare_jid], after_prev_space, next_space)); } catch (InvalidJidError e) { warning("Ignoring search suggestion with invalid Jid: %s", e.message); } } } else { // Other auto complete? } return suggestions; } public Gee.List match_messages(string query, int offset = -1) { Gee.List ret = new ArrayList(); QueryBuilder rows = prepare_search(query, true).limit(10); if (offset > 0) { rows.offset(offset); } foreach (Row row in rows) { try { Message message = new Message.from_row(db, row); Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_for_message(message); ret.add(new MessageItem(message, conversation, row[db.content_item.id])); } catch (InvalidJidError e) { warning("Ignoring search result with invalid Jid: %s", e.message); } } return ret; } public int count_match_messages(string query) { return (int)prepare_search(query, false).select({db.message.id}).count(); } } public class SearchSuggestion : Object { public Account account { get { return conversation.account; } } public Conversation conversation { get; private set; } public Jid? jid { get; private set; } public string completion { get; private set; } public int start_index { get; private set; } public int end_index { get; private set; } public long order { get; set; } public SearchSuggestion(Conversation conversation, Jid? jid, string completion, int start_index, int end_index) { this.conversation = conversation; this.jid = jid; this.completion = completion; this.start_index = start_index; this.end_index = end_index; } } } dino-0.1.0/libdino/src/service/stream_interactor.vala0000644000000000000000000000533613614354364021420 0ustar rootrootusing Gee; using Xmpp; using Dino.Entities; namespace Dino { public class StreamInteractor : Object { public signal void account_added(Account account); public signal void account_removed(Account account); public signal void stream_negotiated(Account account, XmppStream stream); public signal void attached_modules(Account account, XmppStream stream); public ModuleManager module_manager; public ConnectionManager connection_manager; private ArrayList modules = new ArrayList(); public StreamInteractor(Database db) { module_manager = new ModuleManager(db); connection_manager = new ConnectionManager(module_manager); connection_manager.stream_opened.connect(on_stream_opened); } public void connect_account(Account account) { module_manager.initialize(account); account_added(account); connection_manager.connect_account(account); } public async void disconnect_account(Account account) { yield connection_manager.disconnect_account(account); account_removed(account); } public ArrayList get_accounts() { ArrayList ret = new ArrayList(Account.equals_func); foreach (Account account in connection_manager.get_managed_accounts()) { ret.add(account); } return ret; } public XmppStream? get_stream(Account account) { return connection_manager.get_stream(account); } public void add_module(StreamInteractionModule module) { modules.add(module); } public T? get_module(ModuleIdentity? identity) { if (identity == null) return null; foreach (StreamInteractionModule module in modules) { if (identity.matches(module)) return identity.cast(module); } return null; } public new T? get() { foreach (StreamInteractionModule module in modules) { if (module.get_type() == typeof(T)) return (T?) module; } return null; } private void on_stream_opened(Account account, XmppStream stream) { stream.stream_negotiated.connect( (stream) => { stream_negotiated(account, stream); }); } } public class ModuleIdentity : Object { public string id { get; private set; } public ModuleIdentity(string id) { this.id = id; } public T? cast(StreamInteractionModule module) { return module.get_type().is_a(typeof(T)) ? (T?) module : null; } public bool matches(StreamInteractionModule module) { return module.id == id; } } public interface StreamInteractionModule : Object { public abstract string id { get; } } } dino-0.1.0/libdino/src/service/util.vala0000644000000000000000000000105113614354364016636 0ustar rootrootusing Dino.Entities; namespace Dino { public class Util { public static Entities.Message.Type get_message_type_for_conversation(Conversation conversation) { switch (conversation.type_) { case Conversation.Type.CHAT: return Entities.Message.Type.CHAT; case Conversation.Type.GROUPCHAT: return Entities.Message.Type.GROUPCHAT; case Conversation.Type.GROUPCHAT_PM: return Entities.Message.Type.GROUPCHAT_PM; } assert_not_reached(); } } }dino-0.1.0/libdino/src/util.vala0000644000000000000000000000565713614354364015216 0ustar rootrootnamespace Dino { private extern const string SYSTEM_LIBDIR_NAME; private extern const string SYSTEM_PLUGIN_DIR; public class SearchPathGenerator { public string? exec_path { get; private set; } public SearchPathGenerator(string? exec_path) { this.exec_path = exec_path; } public string get_locale_path(string gettext_package, string locale_install_dir) { string? locale_dir = null; if (Path.get_dirname(exec_path).contains("dino") || Path.get_dirname(exec_path) == "." || Path.get_dirname(exec_path).contains("build")) { string exec_locale = Path.build_filename(Path.get_dirname(exec_path), "locale"); if (FileUtils.test(Path.build_filename(exec_locale, "en", "LC_MESSAGES", gettext_package + ".mo"), FileTest.IS_REGULAR)) { locale_dir = exec_locale; } } return locale_dir ?? locale_install_dir; } public string[] get_plugin_paths() { string[] search_paths = new string[0]; if (Environment.get_variable("DINO_PLUGIN_DIR") != null) { search_paths += Environment.get_variable("DINO_PLUGIN_DIR"); } search_paths += Path.build_filename(Environment.get_home_dir(), ".local", "lib", "dino", "plugins"); string? exec_path = this.exec_path; if (exec_path != null) { if (!exec_path.contains(Path.DIR_SEPARATOR_S)) { exec_path = Environment.find_program_in_path(this.exec_path); } if (Path.get_dirname(exec_path).contains("dino") || Path.get_dirname(exec_path) == "." || Path.get_dirname(exec_path).contains("build")) { search_paths += Path.build_filename(Path.get_dirname(exec_path), "plugins"); } if (Path.get_basename(Path.get_dirname(exec_path)) == "bin") { search_paths += Path.build_filename(Path.get_dirname(Path.get_dirname(exec_path)), SYSTEM_LIBDIR_NAME, "dino", "plugins"); } } search_paths += SYSTEM_PLUGIN_DIR; return search_paths; } } public static string get_storage_dir() { return Path.build_filename(Environment.get_user_data_dir(), "dino"); } [CCode (cname = "dino_gettext", cheader_filename = "dino_i18n.h")] public static extern unowned string _(string s); [CCode (cname = "dino_ngettext", cheader_filename = "dino_i18n.h")] public static extern unowned string n(string msgid, string plural, ulong number); [CCode (cname = "bindtextdomain", cheader_filename = "libintl.h")] private static extern unowned string? bindtextdomain(string domainname, string? dirname); [CCode (cname = "bind_textdomain_codeset", cheader_filename = "libintl.h")] private static extern unowned string? bind_textdomain_codeset(string domainname, string? codeset); public static void internationalize(string gettext_package, string locales_dir) { Intl.bind_textdomain_codeset(gettext_package, "UTF-8"); Intl.bindtextdomain(gettext_package, locales_dir); } } dino-0.1.0/main/0000755000000000000000000000000013614354364012074 5ustar rootrootdino-0.1.0/main/CMakeLists.txt0000644000000000000000000001527113614354364014642 0ustar rootrootset(GETTEXT_PACKAGE "dino") find_package(Gettext) include(${GETTEXT_USE_FILE}) gettext_compile(${GETTEXT_PACKAGE} SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/po TARGET_NAME ${GETTEXT_PACKAGE}-translations) find_packages(MAIN_PACKAGES REQUIRED Gee GLib GModule GObject GTK3 ICU ) set(RESOURCE_LIST icons/dino-changes-prevent-symbolic.svg icons/dino-conversation-list-placeholder-arrow.svg icons/dino-double-tick-symbolic.svg icons/dino-emoticon-symbolic.svg icons/dino-qr-code-symbolic.svg icons/dino-party-popper-symbolic.svg icons/dino-status-away.svg icons/dino-status-chat.svg icons/dino-status-dnd.svg icons/dino-status-online.svg icons/im.dino.Dino-symbolic.svg icons/dino-tick-symbolic.svg icons/dino-file-document-symbolic.svg icons/dino-file-download-symbolic.svg icons/dino-file-image-symbolic.svg icons/dino-file-music-symbolic.svg icons/dino-file-symbolic.svg icons/dino-file-table-symbolic.svg icons/dino-file-video-symbolic.svg add_conversation/add_contact_dialog.ui add_conversation/add_groupchat_dialog.ui add_conversation/conference_details_fragment.ui add_conversation/list_row.ui add_conversation/select_jid_fragment.ui chat_input.ui contact_details_dialog.ui conversation_list_titlebar.ui conversation_list_titlebar_csd.ui emojichooser.ui global_search.ui conversation_selector/chat_row_tooltip.ui conversation_selector/conversation_row.ui conversation_summary/image_toolbar.ui conversation_summary/item_metadata_header.ui conversation_summary/view.ui manage_accounts/account_row.ui manage_accounts/add_account_dialog.ui manage_accounts/dialog.ui menu_add.ui menu_app.ui menu_conversation.ui menu_encryption.ui occupant_list.ui occupant_list_item.ui search_autocomplete.ui settings_dialog.ui shortcuts.ui unified_main_content.ui unified_window_placeholder.ui theme.css ) compile_gresources( MAIN_GRESOURCES_TARGET MAIN_GRESOURCES_XML TARGET ${CMAKE_CURRENT_BINARY_DIR}/resources/resources.c TYPE EMBED_C RESOURCES ${RESOURCE_LIST} PREFIX /im/dino/Dino SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/data ) unset(MAIN_EXTRA_OPTIONS) unset(MAIN_EXTRA_PACKAGES) find_package(GDK3) if(GDK3_WITH_X11) set(MAIN_EXTRA_OPTIONS ${MAIN_EXTRA_OPTIONS} -D GDK3_WITH_X11) set(MAIN_EXTRA_PACKAGES ${MAIN_EXTRA_PACKAGES} gdk-x11-3.0) endif(GDK3_WITH_X11) set(MAIN_EXTRA_OPTIONS ${MAIN_EXTRA_OPTIONS} --vapidir=${CMAKE_CURRENT_SOURCE_DIR}/vapi) vala_precompile(MAIN_VALA_C SOURCES src/main.vala src/ui/application.vala src/ui/avatar_drawer.vala src/ui/avatar_image.vala src/ui/chat_input_controller.vala src/ui/conversation_list_titlebar.vala src/ui/conversation_list_titlebar_csd.vala src/ui/global_search.vala src/ui/notifications.vala src/ui/settings_dialog.vala src/ui/unified_window.vala src/ui/unified_window_controller.vala src/ui/add_conversation/add_conference_dialog.vala src/ui/add_conversation/add_contact_dialog.vala src/ui/add_conversation/add_groupchat_dialog.vala src/ui/add_conversation/conference_details_fragment.vala src/ui/add_conversation/conference_list.vala src/ui/add_conversation/list_row.vala src/ui/add_conversation/roster_list.vala src/ui/add_conversation/select_contact_dialog.vala src/ui/add_conversation/select_jid_fragment.vala src/ui/chat_input/edit_history.vala src/ui/chat_input/encryption_button.vala src/ui/chat_input/occupants_tab_completer.vala src/ui/chat_input/smiley_converter.vala src/ui/chat_input/view.vala src/ui/contact_details/blocking_provider.vala src/ui/contact_details/settings_provider.vala src/ui/contact_details/dialog.vala src/ui/contact_details/muc_config_form_provider.vala src/ui/conversation_selector/conversation_selector_row.vala src/ui/conversation_selector/conversation_selector.vala src/ui/conversation_summary/chat_state_populator.vala src/ui/conversation_summary/content_item_widget_factory.vala src/ui/conversation_summary/content_populator.vala src/ui/conversation_summary/conversation_item_skeleton.vala src/ui/conversation_summary/conversation_view.vala src/ui/conversation_summary/date_separator_populator.vala src/ui/conversation_summary/file_widget.vala src/ui/conversation_summary/subscription_notification.vala src/ui/conversation_titlebar/menu_entry.vala src/ui/conversation_titlebar/occupants_entry.vala src/ui/conversation_titlebar/search_entry.vala src/ui/conversation_titlebar/conversation_titlebar.vala src/ui/manage_accounts/account_row.vala src/ui/manage_accounts/add_account_dialog.vala src/ui/manage_accounts/dialog.vala src/ui/occupant_menu/list.vala src/ui/occupant_menu/list_row.vala src/ui/occupant_menu/view.vala src/ui/util/accounts_combo_box.vala src/ui/util/config.vala src/ui/util/data_forms.vala src/ui/util/helper.vala src/ui/util/label_hybrid.vala src/ui/util/preview_file_chooser_native.vala CUSTOM_VAPIS ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi ${CMAKE_BINARY_DIR}/exports/qlite.vapi ${CMAKE_BINARY_DIR}/exports/dino_internal.vapi vapi/emojichooser.vapi PACKAGES ${MAIN_PACKAGES} ${MAIN_EXTRA_PACKAGES} GRESOURCES ${MAIN_GRESOURCES_XML} OPTIONS ${MAIN_EXTRA_OPTIONS} ) add_definitions(${VALA_CFLAGS} -DGETTEXT_PACKAGE=\"${GETTEXT_PACKAGE}\" -DLOCALE_INSTALL_DIR=\"${LOCALE_INSTALL_DIR}\" -DDINO_VERSION=\"${PROJECT_VERSION}\") add_executable(dino ${MAIN_VALA_C} ${MAIN_GRESOURCES_TARGET} src/emojichooser.c) add_dependencies(dino ${GETTEXT_PACKAGE}-translations) target_include_directories(dino PRIVATE src) target_link_libraries(dino libdino ${MAIN_PACKAGES}) if(WIN32) target_link_libraries(dino -mwindows) endif(WIN32) install(TARGETS dino ${TARGET_INSTALL}) install(FILES data/im.dino.Dino.appdata.xml DESTINATION ${APPDATA_FILE_INSTALL_DIR}) install(FILES data/im.dino.Dino.desktop DESTINATION ${DESKTOP_FILE_INSTALL_DIR}) install(FILES data/im.dino.Dino.service DESTINATION ${SERVICE_FILE_INSTALL_DIR}) install(FILES data/icons/im.dino.Dino.svg DESTINATION ${ICON_INSTALL_DIR}/hicolor/scalable/apps) install(FILES data/icons/im.dino.Dino-symbolic.svg DESTINATION ${ICON_INSTALL_DIR}/hicolor/symbolic/apps) install(FILES data/icons/dino-status-away.svg data/icons/dino-status-chat.svg data/icons/dino-status-dnd.svg data/icons/dino-status-online.svg data/icons/dino-changes-prevent-symbolic.svg data/icons/dino-double-tick-symbolic.svg data/icons/dino-qr-code-symbolic.svg data/icons/dino-tick-symbolic.svg DESTINATION ${ICON_INSTALL_DIR}/hicolor/scalable/status ) dino-0.1.0/main/data/0000755000000000000000000000000013614354364013005 5ustar rootrootdino-0.1.0/main/data/add_conversation/0000755000000000000000000000000013614354364016327 5ustar rootrootdino-0.1.0/main/data/add_conversation/add_contact_dialog.ui0000644000000000000000000001613013614354364022451 0ustar rootroot dino-0.1.0/main/data/add_conversation/add_groupchat_dialog.ui0000644000000000000000000002653313614354364023022 0ustar rootroot dino-0.1.0/main/data/add_conversation/conference_details_fragment.ui0000644000000000000000000004221413614354364024370 0ustar rootroot dino-0.1.0/main/data/add_conversation/list_row.ui0000644000000000000000000000623613614354364020537 0ustar rootroot dino-0.1.0/main/data/add_conversation/select_jid_fragment.ui0000644000000000000000000001303713614354364022662 0ustar rootroot dino-0.1.0/main/data/chat_input.ui0000644000000000000000000001033713614354364015506 0ustar rootroot dino-0.1.0/main/data/contact_details_dialog.ui0000644000000000000000000002003313614354364020021 0ustar rootroot dino-0.1.0/main/data/conversation_list_titlebar.ui0000644000000000000000000000434413614354364021004 0ustar rootroot dino-0.1.0/main/data/conversation_list_titlebar_csd.ui0000644000000000000000000000334113614354364021631 0ustar rootroot dino-0.1.0/main/data/conversation_selector/0000755000000000000000000000000013614354364017417 5ustar rootrootdino-0.1.0/main/data/conversation_selector/chat_row_tooltip.ui0000644000000000000000000000144413614354364023341 0ustar rootroot vertical True 0 True vertical True dino-0.1.0/main/data/conversation_selector/conversation_row.ui0000644000000000000000000002254613614354364023370 0ustar rootroot dino-0.1.0/main/data/conversation_summary/0000755000000000000000000000000013614354364017274 5ustar rootrootdino-0.1.0/main/data/conversation_summary/image_toolbar.ui0000644000000000000000000000367713614354364022454 0ustar rootroot end end 10 True True True 25 middle True 5 True none True view-fullscreen-symbolic 1 True dino-0.1.0/main/data/conversation_summary/item_metadata_header.ui0000644000000000000000000000403113614354364023737 0ustar rootroot dino-0.1.0/main/data/conversation_summary/view.ui0000644000000000000000000001077113614354364020613 0ustar rootroot dino-0.1.0/main/data/emojichooser.ui0000644000000000000000000005011313614354364016032 0ustar rootroot dino-0.1.0/main/data/global_search.ui0000644000000000000000000002325613614354364016141 0ustar rootroot dino-0.1.0/main/data/icons/0000755000000000000000000000000013614354364014120 5ustar rootrootdino-0.1.0/main/data/icons/dino-changes-prevent-symbolic.svg0000644000000000000000000000433613614354364022506 0ustar rootroot image/svg+xml Gnome Symbolic Icon Theme Gnome Symbolic Icon Theme dino-0.1.0/main/data/icons/dino-conversation-list-placeholder-arrow.svg0000644000000000000000000000124113614354364024661 0ustar rootroot dino-0.1.0/main/data/icons/dino-double-tick-symbolic.svg0000644000000000000000000000171713614354364021617 0ustar rootroot dino-0.1.0/main/data/icons/dino-emoticon-symbolic.svg0000644000000000000000000000112113614354364021217 0ustar rootroot dino-0.1.0/main/data/icons/dino-file-document-symbolic.svg0000644000000000000000000000073313614354364022145 0ustar rootroot dino-0.1.0/main/data/icons/dino-file-download-symbolic.svg0000644000000000000000000000071113614354364022132 0ustar rootroot dino-0.1.0/main/data/icons/dino-file-image-symbolic.svg0000644000000000000000000000124313614354364021406 0ustar rootroot dino-0.1.0/main/data/icons/dino-file-music-symbolic.svg0000644000000000000000000000211713614354364021445 0ustar rootroot dino-0.1.0/main/data/icons/dino-file-symbolic.svg0000644000000000000000000000064113614354364020327 0ustar rootroot dino-0.1.0/main/data/icons/dino-file-table-symbolic.svg0000644000000000000000000000112713614354364021414 0ustar rootroot dino-0.1.0/main/data/icons/dino-file-video-symbolic.svg0000644000000000000000000000123513614354364021433 0ustar rootroot dino-0.1.0/main/data/icons/dino-party-popper-symbolic.svg0000644000000000000000000001025713614354364022056 0ustar rootroot dino-0.1.0/main/data/icons/dino-qr-code-symbolic.svg0000644000000000000000000000107013614354364020737 0ustar rootroot dino-0.1.0/main/data/icons/dino-status-away.svg0000644000000000000000000000066213614354364020056 0ustar rootroot dino-0.1.0/main/data/icons/dino-status-chat.svg0000644000000000000000000000146713614354364020040 0ustar rootroot dino-0.1.0/main/data/icons/dino-status-dnd.svg0000644000000000000000000000062113614354364017655 0ustar rootroot dino-0.1.0/main/data/icons/dino-status-online.svg0000644000000000000000000000045613614354364020402 0ustar rootroot dino-0.1.0/main/data/icons/dino-tick-symbolic.svg0000644000000000000000000000120313614354364020335 0ustar rootroot dino-0.1.0/main/data/icons/im.dino.Dino-symbolic.svg0000644000000000000000000000407213614354364020710 0ustar rootroot dino-0.1.0/main/data/icons/im.dino.Dino.svg0000644000000000000000000001620613614354364017073 0ustar rootroot dino-0.1.0/main/data/im.dino.Dino.appdata.xml0000644000000000000000000004672213614354364017400 0ustar rootroot im.dino.Dino im.dino.Dino.desktop CC0-1.0 GPL-3.0+ Dino Modern XMPP Chat Client ็Žฐไปฃ XMPP ่Šๅคฉๅฎขๆˆท็ซฏ Modern XMPP-chattklient ะกะพะฒั€ะตะผะตะฝะฝั‹ะน ั‡ะฐั‚-ะบะปะธะตะฝั‚ ะฝะฐ XMPP Client XMPP de discuศ›ii modern Moderno cliente de chat XMPP Nowoczesny komunikator XMPP Client XMPP modรจrn Modernen XMPP-chatcliรซnt Moderne XMPP-chatcliรซnt Moderne XMPP-sludreklient Modernen XMPP Chat Client ็พไปฃ็š„ใช XMPP ใƒใƒฃใƒƒใƒˆ ใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆ Client di chat moderno per XMPP Modern XMPP รœzenetkรผldล‘ Cliente moderno para parolas XMPP Client XMPP moderne XMPP txat bezero modernoa Cliente de XMPP moderno Moderner XMPP Chat Client Client de xat XMPP modern ุชุทุจูŠู‚ ุญุฏูŠุซ ู„ู„ุฏุฑุฏุดุฉ ุนุจุฑ XMPP

Dino is a modern open-source chat client for the desktop. It focuses on providing a clean and reliable Jabber/XMPP experience while having your privacy in mind.

Dino ๆ˜ฏไธ€ไธช็Žฐไปฃ็š„ๅผ€ๆบ่ŠๅคฉๆกŒ้ขๅฎขๆˆท็ซฏใ€‚ๅฎƒ่‡ดๅŠ›ไบŽๆไพ›ไธ€ไธชๆธ…็ˆฝๅˆๅฏ้ ็š„ Jabber/XMPP ไฝ“้ชŒ๏ผŒๅŒๆ—ถๅˆไฟๆŠคๆ‚จ็š„้š็งใ€‚

Dino รคr en moden chattklient fรถr skrivbordet med รถppen kรคllkod. Den erbjuder en elegant och pรฅlitligt upplevelse av Jabber/XMPP samtidigt som den ser efter din integritet.

Dino - ัั‚ะพ ัะพะฒั€ะตะผะตะฝะฝั‹ะน ะบะปะธะตะฝั‚ ะดะปั ั‡ะฐั‚ะพะฒ ั ะพั‚ะบั€ั‹ั‚ั‹ะผ ะธัั…ะพะดะฝั‹ะผ ะบะพะดะพะผ, ะฝะฐะฟั€ะฐะฒะปะตะฝะฝั‹ะน ะฝะฐ ะฝะฐะดั‘ะถะฝะพะต ะธ ะฟั€ะธะฒะฐั‚ะฝะพะต ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะต Jabber/XMPP ะฝะฐ ะฟะตั€ัะพะฝะฐะปัŒะฝั‹ั… ะบะพะผะฟัŒัŽั‚ะตั€ะฐั….

Dino este un client de chat modern, cu sursฤƒ deschisฤƒ, pentru calculatoare. Se concentreazฤƒ sฤƒ furnizeze o experienศ›ฤƒ Jabber/XMPP clarฤƒ ศ™i fiabilฤƒ, ศ›inรขnd cont de confidenศ›ialitatea dumneavoastrฤƒ.

Dino รฉ um moderno chat de cรณdigo aberto para desktop. Ele รฉ focado em prover uma transparente e confiรกvel experiรชncia Jabber/XMPP, tendo em mente a sua privacidade.

Dino jest nowoczesnym, otwartym komunikatorem. Skupia siฤ™ na prostej obsล‚udze sieci Jabber/XMPP dbajฤ…c o twojฤ… prywatnoล›ฤ‡.

Dino es un client de chat liure e modรจrn per lโ€™ordenador de burรจu. Ensaja de provesir una experiรฉncia neta e fisabla de Jabber/XMPP en tรฉner en compte vรฒstra confidencialitat.

Dino is een moderne, vrije chattoepassing voor uw bureaublad. Ze biedt een eenvoudige en betrouwbare Jabber/XMPP-ervaring, met uw privacy in het achterhoofd.

Dino is een moderne, vrije chattoepassing voor uw bureaublad. Ze biedt een eenvoudige en betrouwbare Jabber/XMPP-ervaring, met uw privacy in het achterhoofd.

Dino er en moderne friporg-sludringsklient for skrivebordet. Det fokuserer pรฅ rask og pรฅlitelig XMPP-opplevelse, samtidig som det hegner om personvernet.

Dino assย e modernen, quell-offeneย Chat Client fir den Desktop. Hien biet eng opgeraumtย a robust Jabber/XMPP Erfarungย a leet ee Schwรฉierpunkt op Privatsphรคr.

Dino ใฏใ‚ชใƒผใƒ—ใƒณใ‚ฝใƒผใ‚นใฎ็พไปฃ็š„ใชใƒ‡ใ‚นใ‚ฏใƒˆใƒƒใƒ—ๅ‘ใ‘ใƒใƒฃใƒƒใƒˆใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆใงใ™ใ€‚ใƒ—ใƒฉใ‚คใƒใ‚ทใƒผใ‚’่€ƒๆ…ฎใ—ใคใคใ€ใ‚ทใƒณใƒ—ใƒซใงไฟก้ ผใงใใ‚‹ Jabber/XMPP ใ‚จใ‚ฏใ‚นใƒšใƒชใ‚จใƒณใ‚นใฎๆไพ›ใ‚’็ฌฌไธ€ใซ่€ƒใˆใฆ้–‹็™บใ•ใ‚Œใฆใ„ใพใ™ใ€‚

Dino รจ un client di chat per il desktop, moderno e open-source. Si concentra nel fornire un'esperienza Jabber/XMPP pulita e affidabile tenendo presente la tua privacy.

A Dino egy modern, nyรญlt forrรกskรณdรบ รผzenetkรผldล‘ alkalmazรกs asztali rendszerekre, ami a hangsรบlyt a letisztult รฉs megbรญzhatรณ Jabber/XMPP รฉlmรฉnyre helyezi, mikรถzben a magรกnszfรฉra megล‘rzรฉsรฉt is fontosnak tartja.

Dino รฉ un cliente moderno e de cรณdigo aberto para o escritorio. Orientado a fornecer unha experiencia Jabber/XMPP limpa e fiรกbel tendo a privacidade e seguranza presentes.

Dino est un client de chat libre et moderne pour le bureau. Il tente de fournir une expรฉrience Jabber/XMPP simple et fiable tout en ayant toujours ร  lโ€™esprit votre vie privรฉe.

Dino on nykyaikainen avoimen lรคhdekoodin jutteluohjelma tyรถpรถydรคlle. Se keskittyy tarjoamaan selkeรคn ja luotettavan Jabber/XMPP-kokemuksen unohtamatta yksityisyyttรคsi.

Dino mahaigainerako iturburu irekiko txat bezero moderno bat da. Jabber/XMPP esperientzia garbi eta fidagarri bat ematen du zure pribatutasuna kontuan hartzeaz gain.

Dino es un cliente de mensajerรญa moderno y libre para escritorio. Estรก enfocado en proveer una experiencia Jabber/XMPP limpia y confiable teniendo tu privacidad en mente.

Dino ist ein moderner, quelloffener Chat Client. Er bietet eine aufgerรคumte und robuste Jabber/XMPP Erfahrung und legt einen Schwerpunkt auf Privatsphรคre.

Dino รฉs un client de xat lliure i modern per a l'escriptori. Estร  centrat en proveir una experiรจncia neta i fiable de Jabber/XMPP, sempre tenint en compte la vostra privacitat.

ุฏูŠู†ูˆ ุจุฑู†ุงู…ุฌ ุญุฏูŠุซ ูˆู…ูุชูˆุญ ุงู„ู…ุตุฏุฑ ู„ู„ุฏุฑุฏุดุฉ ุตูู…ู‘ู… ู„ุณุทุญ ุงู„ู…ูƒุชุจ. ูˆูŠูุฑูƒู‘ุฒ ุนู„ูŠ ุชู‚ุฏูŠู… ุชุฌุฑุจุฉ ู†ุธูŠูุฉ ูˆู…ูˆุซูˆู‚ ู…ู†ู‡ุง ู„ุฌุงุจุฑ/XMPP ู…ุน ุฃุฎุฐ ุฎุตูˆุตูŠุชูƒู… ุจุนูŠู† ุงู„ุฅุนุชุจุงุฑ.

It supports end-to-end encryption with OMEMO and OpenPGP and allows configuring privacy-related features such as read receipts and typing notifications.

ๅฎƒๆ”ฏๆŒ OMEMO ๅ’Œ OpenPGP ็ซฏๅฏน็ซฏๅŠ ๅฏ†ๅนถๅ…่ฎธ้…็ฝฎ้š็ง็›ธๅ…ณ็š„็‰นๆ€งๆฏ”ๅฆ‚ๅทฒ่ฏปๅ›žๆ‰งๅ’Œ่พ“ๅ…ฅๆ้†’ใ€‚

Dino stรถdjer end-to-end-kryptering med OMEMO och OpenPGP och tillรฅter konfigurering av funktioner med integritetspรฅverkan som lรคsbekrรคftelser och skriftaviseringar.

ะžะฝ ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ ัะบะฒะพะทะฝะพะต ัˆะธั„ั€ะพะฒะฐะฝะธะต ั‡ะตั€ะตะท OMEMO ะธ OpenPGP ะธ ะฟะพะทะฒะพะปัะตั‚ ะฝะฐัั‚ั€ะฐะธะฒะฐั‚ัŒ ั‚ะฐะบะธะต ั„ัƒะฝะบั†ะธะธ, ะบะฐะบ ัƒะฒะตะดะพะผะปะตะฝะธั ะพ ะฟั€ะพั‡ั‚ะตะฝะธะธ ะธ ะฝะฐะฑะพั€ะต ัะพะพะฑั‰ะตะฝะธะน.

Suportฤƒ criptare de la un capฤƒt la altul prin intermediul OMEMO ศ™i OpenPGP, ศ™i permite configurarea caracteristicilor legate de confidenศ›ialitate precum trimiterea notificฤƒrilor de primire ศ™i tastare.

Suporte criptografia ponta a ponta com OMEMO e OpenPGP e permite configurar privacidadeโ€”caracterรญsticas relacionadas ร s notificaรงรตes de leitura, recebimento e escrita.

Obsล‚uguje szyfrowanie od koล„ca do koล„ca za pomocฤ… OMEMO i OpenPGP, a takลผe daje kontrolฤ™ย nad funkcjami wpล‚ywajฤ…cymi na prywatnoล›ฤ‡, jak powiadomienia o pisaniu czy odczytaniu wiadomoล›ci.

Compatible amb lo chiframent OMEMO e OpenPGP del cap a la fin e permet de configurar de foncionalitats ligadas amb la confidencialitat coma los acusats de lectura e las notificacions dโ€™escritura.

Ze ondersteunt eind-tot-eind-versleuteling met OMEMO en OpenPGP, en laat u toe privacygerelateerde functies, gelijk leesbevestigingen en typmeldingen, in te stellen.

Ze ondersteunt eind-tot-eind-versleuteling met OMEMO en OpenPGP, en staat u toe privacy-gerelateerde functies, zoals leesbevestigingen en typmeldingen, in te stellen.

Det stรธtter ende-til-ende -kryptering med OMEMO og OpenPGP, og tillater oppsett av personvernsrelaterte funksjoner som meldingskvitteringer og skrivevarsling.

Hien รซnnerstรซtzย Enn-zu-Enn Verschlรซsselungย mat OMEMO an OpenPGP an enthรคlt Privatsphรคre-Astellungen zu Liesbestรคtegungenย an Tipp-Benoriichtegungen.

OMEMO ใจ OpenPGP ใ‚’ๅˆฉ็”จใ—ใŸใ‚จใƒณใƒ‰ใƒ„ใƒผใ‚จใƒณใƒ‰ๆš—ๅทๅŒ–ใซๅฏพๅฟœใ—ใฆใŠใ‚Šใ€ๆ—ข่ชญ็Šถๆ…‹ใฎ้€ไฟกใ‚„ๅ…ฅๅŠ›้€š็Ÿฅใชใฉใฎใƒ—ใƒฉใ‚คใƒใ‚ทใƒผ้–ข้€ฃใฎ่จญๅฎšใ‚‚ๅฏ่ƒฝใงใ™ใ€‚

Support la crittografia end-to-end tramite OMEMO e OpenPGP e permette di configurare le funzioni relative alla privacy come le ricevute di lettura e le notifiche di digitazione.

Tรกmogatja a vรฉgponttรณl-vรฉgpontig titkosรญtรกst az OMEMO รฉs az OpenPGP รกltal, รฉs magรกnszfรฉrรกhoz kรถtล‘dล‘ beรกllรญtรกsi lehetล‘sรฉgeket is biztosรญt, mint pรฉldรกul a kรฉzbesรญtรฉsi, vagy gรฉpelรฉsi รฉrtesรญtรฉsek kรผldรฉse.

Suporta o cifrado de punto-a-punto con OMEMO e OpenPGP e permite configurar trazos orientados รก privacidade tales coma confirmaciรณn de lectura e notificaciรณns de escritura.

Il prend en charge le chiffrement de bout en bout avec OMEMO et OpenPGP et permet de configurer les fonctions liรฉes ร  la vie privรฉe telles que lโ€™accusรฉ de rรฉception et les notifications de frappe.

Se tukee pรครคstรค pรครคhรคn -salausta OMEMO:n ja OpenPGP:n avulla ja mahdollistaa yksityisyyteen liittyvien ominaisuuksien, kuten lukukuittausten ja kirjoitusilmoitusten asetusten mรครคrittรคmisen.

Amaieratik amaierarako enkriptazioa onartzen du OMEMO eta OpenPGPrekin eta pribatutasun ezaugarriak konfiguratzea baimentzen du irakurtze markak eta idazketa jakinarazpenak bezala.

Soporta cifrado de extremo a extremo a travรฉs de OMEMO y OpenPGP y permite configurar las caracterรญsticas relacionadas con la privacidad, como confirmaciones de lectura y notificaciones de escritura.

Er unterstรผtzt Ende-zu-Ende Verschlรผsselung mit OMEMO und OpenPGP und enthรคlt Privatsphรคre-Einstellungen zu Lesebestรคtigungen und Tippbenachrichtigungen.

Implementa xifratge punt a punt amb OMEMO i OpenPGP, i permet configurar funcionalitats relacionades amb la privacitat com per exemple rebuts de lectura i notificacions d'escriptura.

ูˆู‡ูˆ ูŠุฏุนู… ุงู„ุชุดููŠุฑ ุจูˆุงุณุทุฉ OMEMO ูˆ OpenPGP ูŠุณู…ุญ ุจุฅุนุฏุงุฏ ู…ูŠุฒุงุช ุงู„ุฎุตูˆุตูŠุฉ ูƒุงู„ุฅูŠุตุงู„ุงุช ุงู„ู…ู‚ุฑูˆุกุฉ ูˆุงู„ุฅุฎุทุงุฑุงุช ุนู†ุฏ ุงู„ูƒุชุงุจุฉ.

Dino fetches history from the server and synchronizes messages with other devices.

Dino ไปŽๆœๅŠกๅ™จ่Žทๅ–ๆถˆๆฏๅนถๅ’Œๅ…ถไป–่ฎพๅค‡ๅŒๆญฅใ€‚

Dino hรคmtar historik frรฅn servern och synkroniserar meddelanden med andra enheter.

Dino ะทะฐะณั€ัƒะถะฐะตั‚ ะธัั‚ะพั€ะธัŽ ั ัะตั€ะฒะตั€ะฐ ะธ ัะธะฝั…ั€ะพะฝะธะทะธั€ัƒะตั‚ ัะพะพะฑั‰ะตะฝะธั ั ะดั€ัƒะณะธะผะธ ัƒัั‚ั€ะพะนัั‚ะฒะฐะผะธ.

Dino preia istoricul discuศ›iilor de pe server ศ™i sincronizeazฤƒ mesajele cu celelalte dispozitive.

Dino obtรฉm o histรณrico do servidor e sincroniza mensagens com outros dispositivos.

Dino pobiera historiฤ™ rozmรณw z serwera i synchronizuje wiadomoล›ci z innymi urzฤ…dzeniami.

Dino recupรจra lโ€™istoric del servidor e sincroniza los messatges amb dโ€™autres periferics.

Dino haalt de geschiedenis op van de server en synchroniseert berichten met andere apparaten.

Dino haalt de geschiedenis op van de server en synchroniseert berichten met andere apparaten.

Dino henter historikk fra tjeneren og synkroniserer meldinger med andre enheter.

Dino rifftย  Gesprรฉichverlรคf vum Server of a synchronisรฉiert Noriichteย mat anereย Gerรคter.

Dino ใฏใ‚ตใƒผใƒใƒผใ‹ใ‚‰ๅฑฅๆญดใ‚’ๅ–ๅพ—ใ—ใ€ใปใ‹ใฎใƒ‡ใƒใ‚คใ‚นใจใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’ๅŒๆœŸใ—ใพใ™ใ€‚

Dino recupera la cronologia dal server e sincronizza i messaggi con gli altri dispositivi.

A Dino lekรฉri a chat elล‘zmรฉnyeket a szerverrล‘l, รฉs szinkronizรกlja az รผzeneteket a tรถbbi eszkรถzzel.

Dino obtรฉn o histรณrico dende o servidor e sincroniza as mensaxes con outros dispositivos.

Dino rรฉcupรจre lโ€™historique du serveur et synchronise les messages avec d'autres appareils.

Dino hakee historian palvelimelta ja synkronisoi viestit muiden laitteiden kanssa.

Dinok zerbitzaritik hartzen du historia eta beste gailuekin mezuak sinkronizatzen ditu.

Dino recupera los mensajes desde el servidor y sincroniza los mensajes con otros dispositivos.

Dino ruft Gesprรคchsverlรคufe vom Server ab und synchronisiert Nachrichten mit anderen Gerรคten.

Dino recupera l'historial del servidor i sincronitza els missatges amb altres dispositius.

ูŠู‚ูˆู… ุฏูŠู†ูˆ ุจุฌู„ุจ ุงู„ุณูุฌู„ู‘ ู…ูู† ุงู„ุณูŠุฑูุฑ ุซู… ูŠูุฒุงู…ูู† ุงู„ุฑุณุงุฆู„ ู…ุน ุงู„ุฃุฌู‡ุฒุฉ ุงู„ุฃุฎุฑู‰.

Main window with conversations ๅธฆๆœ‰ๅฏน่ฏ็š„ไธป็ช—ๅฃ Huvudfรถnster med konversationer ะ“ะปะฐะฒะฝะพะต ะพะบะฝะพ ั ั‡ะฐั‚ะฐะผะธ Fereastra principalฤƒ de conversaศ›ii Janela principal com as conversas Gล‚รณwne okno rozmรณw Fenรจstra mร ger amb conversacions Hoofdvenster met gesprekken Hoofdvenster met gesprekken Hovedvindu med samtaler Haaptfรซnsterย mat den Conversatiounen ใƒˆใƒผใ‚ฏไธญใฎใƒกใ‚คใƒณใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆ La finestra principale con le conversazioni A fล‘ ablak a beszรฉlgetรฉsekkel Lapela principal con parolas Fenรชtre principale avec des conversations Keskustelut pรครคikkunassa Leiho nagusia elkarrizketekin Ventana principal con conversaciones Hauptfenster mit Konversationen Finestra principal amb converses ุงู„ู†ุงูุฐุฉ ุงู„ุฑุฆูŠุณูŠุฉ ุจุงู„ู…ูุญุงุฏุซุงุช https://dino.im/img/appdata/main.png Start Conversation ๅผ€ๅง‹่Šๅคฉ Starta Chatt ะะฐั‡ะฐั‚ัŒ ั‡ะฐั‚ Porneศ™te o conversaศ›ie Iniciar conversa Rozpocznij rozmowฤ™ Comenรงar la discussion Gesprek beginnen Gesprek beginnen Start samtale Konversatioun starten ใƒˆใƒผใ‚ฏใ‚’้–‹ๅง‹ Inizia una Conversazione Csevegรฉs kezdemรฉnyezรฉse Comezar conversa Commencer une discussion Aloita keskustelu Elkarrizketa hasi Iniciar Conversaciรณn Komenci Konversacio Unterhaltung starten Inicia una conversa ุงู„ุดุฑูˆุน ููŠ ู…ุญุงุฏุซุฉ https://dino.im/img/appdata/start_chat.png Contact Details ่”็ณปไบบ่ฏฆๆƒ… Kontaktdetaljer ะ˜ะฝั„ะพั€ะผะฐั†ะธั ะพ ะบะพะฝั‚ะฐะบั‚ะต Detalii contact Detalhes do contato Szczegรณล‚y kontaktu Detalhs del contacte Contactgegevens Contactgegevens Kontaktdetaljer Kontaktdetailer ้€ฃ็ตกๅ…ˆใฎ่ฉณ็ดฐ Dettagli del contatto Felhasznรกlรณ informรกciรณ Detalles do contacto Informations du contact Yhteystiedot Kontaktuaren xehetasunak Detalles del contacto Kontaktaj Detaloj Kontaktdetails Detalls del contacte ุชูุงุตูŠู„ ุนู† ุงู„ู…ูุฑุงุณู„ https://dino.im/img/appdata/contact_details.png dino Dino Development Team https://dino.im https://github.com/dino/dino/issues https://liberapay.com/Dino https://hosted.weblate.org/projects/dino/ appstream@dino.im
dino-0.1.0/main/data/im.dino.Dino.desktop0000644000000000000000000000046013614354364016625 0ustar rootroot[Desktop Entry] Version=1.0 Name=Dino GenericName=Jabber/XMPP Client Keywords=chat;talk;im;message;xmpp;jabber; Exec=dino %U Icon=im.dino.Dino StartupNotify=false Terminal=false Type=Application Categories=GTK;Network;Chat;InstantMessaging; X-GNOME-UsesNotifications=true MimeType=x-scheme-handler/xmpp; dino-0.1.0/main/data/im.dino.Dino.service0000644000000000000000000000005413614354364016613 0ustar rootroot[D-BUS Service] Name=im.dino.Dino Exec=dino dino-0.1.0/main/data/manage_accounts/0000755000000000000000000000000013614354364016134 5ustar rootrootdino-0.1.0/main/data/manage_accounts/account_row.ui0000644000000000000000000000266413614354364021026 0ustar rootroot dino-0.1.0/main/data/manage_accounts/add_account_dialog.ui0000644000000000000000000014764613614354364022300 0ustar rootroot dino-0.1.0/main/data/manage_accounts/dialog.ui0000644000000000000000000005112513614354364017736 0ustar rootroot dino-0.1.0/main/data/menu_add.ui0000644000000000000000000000106513614354364015122 0ustar rootroot
app.add_chat Start Conversation
app.add_conference Join Channel
dino-0.1.0/main/data/menu_app.ui0000644000000000000000000000164213614354364015153 0ustar rootroot
app.accounts Accounts app.settings Settings
app.open_shortcuts Keyboard Shortcuts app.about About Dino
dino-0.1.0/main/data/menu_conversation.ui0000644000000000000000000000053213614354364017102 0ustar rootroot
app.contact_details Contact Details
dino-0.1.0/main/data/menu_encryption.ui0000644000000000000000000000275413614354364016572 0ustar rootroot False True False vertical 10 Unencrypted True True False True True False True 0 main dino-0.1.0/main/data/occupant_list.ui0000644000000000000000000000301413614354364016211 0ustar rootroot dino-0.1.0/main/data/occupant_list_item.ui0000644000000000000000000000424013614354364017231 0ustar rootroot dino-0.1.0/main/data/search_autocomplete.ui0000644000000000000000000000166513614354364017402 0ustar rootroot horizontal True 4 6 6 24 24 True False True end dino-0.1.0/main/data/settings_dialog.ui0000644000000000000000000000750413614354364016531 0ustar rootroot dino-0.1.0/main/data/shortcuts.ui0000644000000000000000000000526713614354364015414 0ustar rootroot True True shortcuts True General True <ctrl>T Start Conversation True <ctrl>G Join Channel True Navigation True <ctrl>Tab Jump to next conversation True <ctrl><Shift>Tab Jump to previous conversation dino-0.1.0/main/data/theme.css0000644000000000000000000000713513614354364014627 0ustar rootroot/** * This theme file is applied after the operating system theme * It provides sane defaults for things that are very Dino-specific. */ window.dino-main .dino-header-right { background: @theme_base_color; } window.dino-main .dino-header-left { background: @insensitive_bg_color; } window.dino-main headerbar.dino-left label.title { opacity: 0; font-size: 0; color: transparent; } window.dino-main .dino-conversation { background: @theme_base_color; } window.dino-main .dino-conversation undershoot, window.dino-main .dino-conversation viewport /* Some themes set this */ { background: none; } @keyframes highlight { from { background: alpha(@warning_color, 0.5) } to { background: transparent } } window.dino-main .dino-conversation .highlight-once { animation-duration: 3s; animation-timing-function: ease-out; animation-iteration-count: 1; animation-name: highlight; } window.dino-main .dino-conversation .message-box:hover { background: alpha(@theme_fg_color, 0.04); } window.dino-main .dino-sidebar > frame { background: @insensitive_bg_color; border-left: 1px solid @borders; border-bottom: 1px solid @borders; } .message-box { transition: background .05s ease; } .file-box-outer { background: @theme_base_color; border-radius: 3px; border: 1px solid alpha(@theme_fg_color, 0.1); } .file-box { margin: 12px; } window.dino-main .dino-sidebar > frame.collapsed { border-bottom: 1px solid @borders; } window.dino-main .dino-sidebar frame.auto-complete { background: @theme_base_color; } window.dino-main .dino-sidebar frame.auto-complete list > row { transition: none; } window.dino-main .dino-chatinput frame box { background: transparent; } window.dino-main button.dino-attach-button { min-width: 24px; /* Make button the same with as avatars */ } window.dino-main button.dino-chatinput-button { border: none; background: transparent; box-shadow: none; min-height: 0; padding: 7px 5px; color: alpha(@theme_fg_color, 0.6); outline: none; } window.dino-main button.dino-chatinput-button:hover { color: @theme_selected_bg_color; } window.dino-main button.dino-chatinput-button:backdrop { color: alpha(@theme_unfocused_fg_color, 0.6); } window.dino-main button.dino-chatinput-button:active, window.dino-main button.dino-chatinput-button:checked { color: alpha(@theme_selected_bg_color, 0.8); } window.dino-main button.dino-chatinput-button:checked:backdrop { color: alpha(@theme_unfocused_selected_bg_color, 0.8); } .dino-chatinput textview, .dino-chatinput textview text { background-color: @theme_base_color; } /*Chat input warning*/ box.dino-input-warning frame border { border-color: @warning_color; } box.dino-input-warning frame separator { background-color: @warning_color; border: none; } box.dino-input-warning label { color: mix(@warning_color, @theme_fg_color, 0.5); } /*Chat input error*/ box.dino-input-error frame border { border-color: @error_color; } box.dino-input-error frame separator { background-color: @error_color; border: none; } box.dino-input-error label { color: @error_color; } @keyframes input-error-highlight { 0% { color: mix(@error_color, @theme_fg_color, 0.3);} 30% { color: @error_color; text-shadow: 0px 0px 2px alpha(@error_color, 0.4); } 100% { color: mix(@error_color, @theme_fg_color, 0.3); } } box.dino-input-error label.input-status-highlight-once { animation-duration: 1s; animation-timing-function: linear; animation-iteration-count: 1; animation-name: input-error-highlight; } dino-0.1.0/main/data/unified_main_content.ui0000644000000000000000000002520413614354364017530 0ustar rootroot 300 horizontal True True True never True True content 20 10 start start True True start True 70 50 True end Click here to start a conversation or join a channel. placeholder False False True True True vertical True True True end end crossfade True 30 70 False end end True go-down-symbolic 1 True content vertical True center center True im.dino.Dino-symbolic 144 30 True True You have no open chats 0.5 0.5 placeholder True end slide-left True 400 none True True False dino-0.1.0/main/data/unified_window_placeholder.ui0000644000000000000000000000605613614354364020727 0ustar rootroot dino-0.1.0/main/po/0000755000000000000000000000000013614354364012512 5ustar rootrootdino-0.1.0/main/po/LINGUAS0000644000000000000000000000012713614354364013537 0ustar rootrootar ca de en eo es eu fi fr gl hu it ja lb nb nl nl_BE oc pl pt_BR ro ru sv zh_CN zh_TW dino-0.1.0/main/po/ar.po0000644000000000000000000006436313614354364013470 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2020-01-12 13:21+0000\n" "Language-Team: Arabic \n" "Language: ar\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " "&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n" "X-Generator: Weblate 3.10.1\n" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "Image sent" msgstr "ุชู… ุฅุฑุณุงู„ ุงู„ุตูˆุฑุฉ" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "File sent" msgstr "ุชู… ุฅุฑุณุงู„ ุงู„ู…ู„ู" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "Image received" msgstr "ุชู… ุงุณุชู„ุงู… ุงู„ุตูˆุฑุฉ" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "File received" msgstr "ุชู… ุงุณุชู„ุงู… ุงู„ู…ู„ู" #: main/src/ui/notifications.vala:98 msgid "Subscription request" msgstr "ุทู„ุจ ุงุดุชุฑุงูƒ" #: main/src/ui/notifications.vala:105 main/src/ui/notifications.vala:140 #: main/src/ui/conversation_summary/subscription_notification.vala:37 msgid "Accept" msgstr "ู‚ุจูˆู„" #: main/src/ui/notifications.vala:106 main/src/ui/notifications.vala:139 #: main/src/ui/conversation_summary/subscription_notification.vala:38 msgid "Deny" msgstr "ุฑูุถ" #: main/src/ui/notifications.vala:112 #: main/src/ui/manage_accounts/add_account_dialog.vala:258 #: main/src/ui/add_conversation/conference_details_fragment.vala:188 #, c-format msgid "Could not connect to %s" msgstr "ู„ุง ูŠู…ูƒู† ุงู„ุฅุชุตุงู„ ุจู€ %s" #: main/src/ui/notifications.vala:128 #, c-format msgid "Invitation to %s" msgstr "ุฏุนูˆุฉ ุฅู„ู‰ %s" #: main/src/ui/notifications.vala:129 #, c-format msgid "%s invited you to %s" msgstr "ุฏุนุงูƒ %s ุฅู„ู‰ %s" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "ุฅุฒุงู„ุฉ ุงู„ุญุณุงุจ %s ุŸ" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "ุญุฐู" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "ุงุฎุชูŠุงุฑ ุงู„ุตูˆุฑุฉ ุงู„ุฑู…ุฒูŠุฉ" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "ุงุฎุชูŠุงุฑ" #: main/src/ui/manage_accounts/dialog.vala:152 main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "ุฅู„ุบุงุก" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "ุงู„ุตูˆุฑ" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "ูƒุงูุฉ ุงู„ู…ู„ูุงุช" #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connectingโ€ฆ" msgstr "ุงู„ุฅุชุตุงู„ ุฌุงุฑู โ€ฆ" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "ู…ุชุตู„" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "ุบูŠุฑ ู…ุชุตู„" #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "ูƒู„ู…ุฉ ุงู„ู…ุฑูˆุฑ ุฎุงุทุฆุฉ" #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "ุดู‡ุงุฏุฉ TLS ุบูŠุฑ ุตุงู„ุญุฉ" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "ุฎุทุฃ" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "ุฅุถุงูุฉ ุญุณุงุจ" #: main/src/ui/manage_accounts/add_account_dialog.vala:166 #, c-format msgid "Sign in to %s" msgstr "ุงู„ุฅุชุตุงู„ ุจู€ %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:210 #, c-format msgid "You can now start using %s" msgstr "ุจุฅู…ูƒุงู†ูƒ ุงู„ุขู† ุงู„ุดุฑูˆุน ููŠ ุงุณุชุฎุฏุงู… %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:263 #: main/src/ui/manage_accounts/add_account_dialog.vala:296 #: main/src/ui/manage_accounts/add_account_dialog.vala:306 #: main/src/ui/manage_accounts/add_account_dialog.vala:382 #: main/src/ui/add_conversation/conference_details_fragment.vala:191 msgid "Invalid address" msgstr "ุงู„ุนู†ูˆุงู† ุบูŠุฑ ุตุงู„ุญ" #: main/src/ui/manage_accounts/add_account_dialog.vala:278 msgid "Wrong username or password" msgstr "ุงุณู… ุงู„ู…ุณุชุฎุฏู… ุฃูˆ ูƒู„ู…ุฉ ุงู„ุณุฑ ุบูŠุฑ ุตุญูŠุญุฉ" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Something went wrong" msgstr "ุญุฏุซ ุฎุทุฃ ู…ุง" #: main/src/ui/manage_accounts/add_account_dialog.vala:321 msgid "No response from server" msgstr "ู„ุง ุฑุฏู‘ ู…ูู† ุทุฑู ุงู„ุฎุงุฏู…" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 #, c-format msgid "Register on %s" msgstr "ู‚ู… ุจุงู„ุชุณุฌูŠู„ ุนู„ู‰ %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:330 msgid "The server requires to sign up through a website" msgstr "ูŠุชุทู„ุจ ุงู„ุฎุงุฏู… ุงู„ุชุณุฌูŠู„ ู…ู† ุฎู„ุงู„ ู…ูˆู‚ุน ูˆูŠุจ" #: main/src/ui/manage_accounts/add_account_dialog.vala:332 msgid "Open Registration" msgstr "ุงู„ุชุณุฌูŠู„ุงุช ู…ูุชูˆุญุฉ" #: main/src/ui/manage_accounts/add_account_dialog.vala:344 msgid "Register" msgstr "ุฅู†ุดุงุก ุญุณุงุจ" #: main/src/ui/manage_accounts/add_account_dialog.vala:346 #, c-format msgid "Check %s for information on how to sign up" msgstr "ุฅุทู‘ู„ุน ุนู„ู‰ %s ู„ู„ู…ุฒูŠุฏ ู…ู† ุงู„ู…ุนู„ูˆู…ุงุช ุญูˆู„ ุงู„ุชุณุฌูŠู„" #: main/src/ui/conversation_selector/conversation_selector_row.vala:146 #: main/src/ui/conversation_selector/conversation_selector_row.vala:160 #: main/src/ui/util/helper.vala:136 main/src/ui/util/helper.vala:151 msgid "Me" msgstr "ุฃู†ุง" #: main/src/ui/conversation_selector/conversation_selector_row.vala:258 #: main/src/ui/conversation_summary/date_separator_populator.vala:100 #, no-c-format msgid "%b %d" msgstr "%b %d" #: main/src/ui/conversation_selector/conversation_selector_row.vala:262 #: main/src/ui/conversation_summary/date_separator_populator.vala:91 msgid "Yesterday" msgstr "ุงู„ุจุงุฑุญุฉ" #: main/src/ui/conversation_selector/conversation_selector_row.vala:265 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:205 #, no-c-format msgid "%Hโˆถ%M" msgstr "%Hโˆถ%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:266 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:206 #, no-c-format msgid "%lโˆถ%M %p" msgstr "%lโˆถ%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:269 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:210 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "ู…ู†ุฐ ุฏู‚ูŠู‚ุฉ" msgstr[1] "ู…ู†ุฐ %i ุฏ" msgstr[2] "ู…ู†ุฐ ุฏู‚ูŠู‚ุชูŠู†" msgstr[3] "ู…ู†ุฐ %i ุฏ" msgstr[4] "ู…ู†ุฐ %i ุฏู‚ุงุฆู‚" msgstr[5] "ู…ู†ุฐ %i ุฏู‚ูŠู‚ุฉ" #: main/src/ui/conversation_selector/conversation_selector_row.vala:271 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:212 msgid "Just now" msgstr "ุงู„ุขู†" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/unified_window.vala:249 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "ุงู„ุฅู„ุชุญุงู‚ ุจู‚ู†ุงุฉ" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 msgid "Join" msgstr "ุฅู†ุถู…ู‘" #: main/src/ui/util/helper.vala:126 #, c-format msgid "%s from %s" msgstr "%s ู…ูู† %s" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:28 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "ุฅุถุงูุฉ" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:42 msgid "Save" msgstr "ุญูุธ" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "ุงู„ุชุงู„ูŠ" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "ุงู„ุนูˆุฏุฉ" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/src/ui/unified_window.vala:248 #: main/data/conversation_list_titlebar_csd.ui:12 #: main/data/conversation_list_titlebar.ui:16 main/data/menu_add.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:20 main/data/shortcuts.ui:17 msgid "Start Conversation" msgstr "ุงู„ุดุฑูˆุน ููŠ ู…ุญุงุฏุซุฉ" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "ุฅุจุฏุฃ" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joiningโ€ฆ" msgstr "ููŠ ุทุฑูŠู‚ ุงู„ุฅู„ุชุญุงู‚ โ€ฆ" #: main/src/ui/add_conversation/conference_details_fragment.vala:169 msgid "Password required to enter room" msgstr "ูƒู„ู…ุฉ ุงู„ุณุฑ ู…ุทู„ูˆุจุฉ ู„ู„ุฏุฎูˆู„ ุฅู„ู‰ ุบุฑูุฉ ุงู„ู…ุญุงุฏุซุฉ" #: main/src/ui/add_conversation/conference_details_fragment.vala:174 msgid "Banned from joining or creating conference" msgstr "ู…ุญุธูˆุฑ ู…ูู† ุงู„ุฅู†ุถู…ุงู… ุฃูˆ ุฅู†ุดุงุก ููุฑูŽู‚ ู…ุญุงุฏุซุฉ" #: main/src/ui/add_conversation/conference_details_fragment.vala:176 msgid "Room does not exist" msgstr "ุบุฑูุฉ ุงู„ู…ุญุงุฏุซุฉ ุบูŠุฑ ู…ูˆุฌูˆุฏุฉ" #: main/src/ui/add_conversation/conference_details_fragment.vala:178 msgid "Not allowed to create room" msgstr "ู„ูŠุณ ู„ุฏูŠูƒ ุชุณุฑูŠุญ ู„ุฅู†ุดุงุก ุบุฑูุฉ ู…ุญุงุฏุซุฉ" #: main/src/ui/add_conversation/conference_details_fragment.vala:180 msgid "Members-only room" msgstr "ุงู„ุบุฑูุฉ ุฎุงุตุฉ ุจุฃุนุถุงุฆู‡ุง ูู‚ุท" #: main/src/ui/add_conversation/conference_details_fragment.vala:183 msgid "Choose a different nick" msgstr "ุฅุฎุชุฑ ุฅุณู…ุง ู…ุณุชุนุงุฑุง ุขุฎุฑ" #: main/src/ui/add_conversation/conference_details_fragment.vala:185 msgid "Too many occupants in room" msgstr "ุบุฑูุฉ ุงู„ู…ุญุงุฏุซุฉ ู…ูƒุชุถุฉ" #: main/src/ui/unified_window.vala:227 msgid "Welcome to Dino!" msgstr "ู…ุฑุญุจุง ุจูƒู… ุนู„ู‰ ุฏูŠู†ูˆ!" #: main/src/ui/unified_window.vala:228 msgid "Sign in or create an account to get started." msgstr "ุฅุชู‘ุตู„ ุฃูˆ ู‚ู… ุจุฅู†ุดุงุก ุญุณุงุจ ู„ู„ู…ูˆุงุตู„ุฉ." #: main/src/ui/unified_window.vala:229 msgid "Set up account" msgstr "ุชู‡ูŠุฆุฉ ุงู„ุญุณุงุจ" #: main/src/ui/unified_window.vala:237 msgid "No active accounts" msgstr "ู„ูŠุณ ู‡ู†ุงูƒ ุฃูŠุฉ ุญุณุงุจุงุช ู†ุดุทุฉ" #: main/src/ui/unified_window.vala:238 msgid "Manage accounts" msgstr "ุฅุฏุงุฑุฉ ุงู„ุญุณุงุจุงุช" #: main/src/ui/unified_window.vala:247 msgid "No active conversations" msgstr "ู„ูŠุณ ู‡ู†ุงูƒ ุฃูŠุฉ ู…ุญุงุฏุซุงุช ู†ุดุทุฉ" #: main/src/ui/conversation_summary/file_widget.vala:275 #, c-format msgid "Downloading %sโ€ฆ" msgstr "ุฌุงุฑู ุชู†ุฒูŠู„ %sโ€ฆ" #: main/src/ui/conversation_summary/file_widget.vala:281 #, c-format msgid "%s offered: %s" msgstr "ุนุฑุถ ุนู„ูŠูƒ %s: %s" #: main/src/ui/conversation_summary/file_widget.vala:283 #, c-format msgid "File offered: %s" msgstr "ุงู„ู…ู„ู ุงู„ู…ู‚ุฏู‘ู…: %s" #: main/src/ui/conversation_summary/file_widget.vala:285 msgid "File offered" msgstr "ุชู… ุชู‚ุฏูŠู… ุงู„ู…ู„ู" #: main/src/ui/conversation_summary/file_widget.vala:290 msgid "File transfer failed" msgstr "ูุดู„ ู†ู‚ู„ ุงู„ู…ู„ู" #: main/src/ui/conversation_summary/date_separator_populator.vala:85 msgid "Today" msgstr "ุงู„ูŠูˆู…" #: main/src/ui/conversation_summary/date_separator_populator.vala:98 msgid "%a, %b %d" msgstr "%a, %b %d" #: main/src/ui/conversation_summary/chat_state_populator.vala:145 #, c-format msgid "%s, %s and %i others" msgstr "%s ูˆ %s ูˆ %i ุขุฎูŽุฑูŠู†" #: main/src/ui/conversation_summary/chat_state_populator.vala:147 #, c-format msgid "%s, %s and %s" msgstr "%s, %s ูˆ %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:149 #, c-format msgid "%s and %s" msgstr "%s ูˆ %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:154 msgid "is typingโ€ฆ" msgid_plural "are typingโ€ฆ" msgstr[0] "ูŠูƒุชุจโ€ฆ" msgstr[1] "ูŠูƒุชุจโ€ฆ" msgstr[2] "ูŠูƒุชูุจุงู†โ€ฆ" msgstr[3] "ูŠูƒุชุจูˆู†โ€ฆ" msgstr[4] "ูŠูƒุชุจูˆู†โ€ฆ" msgstr[5] "ูŠูƒุชุจูˆู†โ€ฆ" #: main/src/ui/conversation_summary/chat_state_populator.vala:156 msgid "has stopped typing" msgstr "ุชูˆู‚ููŽ ุนู† ุงู„ูƒุชุงุจุฉ" #: main/src/ui/conversation_summary/content_item_widget_factory.vala:59 msgid "Message too long" msgstr "ุงู„ุฑุณุงู„ุฉ ุทูˆูŠู„ุฉ ุฌุฏู‹ุง" #: main/src/ui/conversation_summary/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "ูŠุฑูŠุฏ ู‡ุฐุง ุงู„ู…ุฑุงุณู„ ุฅุถุงูุชูƒ ุฅู„ู‰ ู‚ุงุฆู…ุฉ ู…ุฑุงุณู„ูŠู‡" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:193 #, no-c-format msgid "%x, %Hโˆถ%M" msgstr "%x, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:194 #, no-c-format msgid "%x, %lโˆถ%M %p" msgstr "%x, %lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:197 #, no-c-format msgid "%b %d, %Hโˆถ%M" msgstr "%b %d, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:198 #, no-c-format msgid "%b %d, %lโˆถ%M %p" msgstr "%b %d, %lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:201 #, no-c-format msgid "%a, %Hโˆถ%M" msgstr "%a, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:202 #, no-c-format msgid "%a, %lโˆถ%M %p" msgstr "%a, %lโˆถ%M %p" #: main/src/ui/occupant_menu/list.vala:102 msgid "Owner" msgstr "ุงู„ู…ุงู„ูƒ" #: main/src/ui/occupant_menu/list.vala:104 msgid "Admin" msgstr "ุงู„ู…ุฏูŠุฑ" #: main/src/ui/occupant_menu/list.vala:106 msgid "Member" msgstr "ุนุถูˆ" #: main/src/ui/occupant_menu/list.vala:108 msgid "User" msgstr "ู…ุณุชุฎุฏูู…" #: main/src/ui/occupant_menu/view.vala:29 #: main/src/ui/occupant_menu/view.vala:38 msgid "Invite" msgstr "ุฏุนูˆุฉ" #: main/src/ui/occupant_menu/view.vala:37 msgid "Invite to Conference" msgstr "ุฏุนูˆุฉ ุฅู„ู‰ ูุฑูŠู‚ ู…ุญุงุฏุซุฉ" #: main/src/ui/occupant_menu/view.vala:88 msgid "Start private conversation" msgstr "ุงู„ุดุฑูˆุน ููŠ ู…ุญุงุฏุซุฉ ุฎุงุตุฉ" #: main/src/ui/occupant_menu/view.vala:96 msgid "Kick" msgstr "ุทุฑุฏ" #: main/src/ui/global_search.vala:140 #, c-format msgid "%i search results" msgstr "%i ู†ุชูŠุฌุฉ ุจุญุซ" #: main/src/ui/global_search.vala:167 #, c-format msgid "In %s" msgstr "ููŠ %s" #: main/src/ui/global_search.vala:167 #, c-format msgid "With %s" msgstr "ู…ุน %s" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "ุชูุงุตูŠู„ ูุฑูŠู‚ ุงู„ู…ุญุงุฏุซุฉ" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:24 msgid "Contact Details" msgstr "ุชูุงุตูŠู„ ุนู† ุงู„ู…ูุฑุงุณู„" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "ุฅุณู… ุบุฑูุฉ ุงู„ู…ุญุงุฏุซุฉ" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "ูˆุตู ุบุฑูุฉ ุงู„ู…ุญุงุฏุซุฉ" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "ุฏุงุฆู…ุฉ" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "ุณูˆู ุชุจู‚ู‰ ุงู„ุบุฑูุฉ ุญุชู‰ ูˆ ุฅู† ุบุงุฏุฑู‡ุง ุขุฎูุฑ ู…ู‚ูŠู…" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "ูŠู…ูƒู† ุงู„ุจุญุซ ุนู†ู‡ุง ู„ู„ุนู„ู†" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "ูŠู…ูƒู† ู„ู„ู…ู‚ูŠู…ูŠู† ุชุบูŠูŠุฑ ุงู„ู…ูˆุถูˆุน" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Discover real JIDs" msgstr "ุงุณุชูƒุดุงู ู…ูุนุฑู‘ููŠ JID ุงู„ุญู‚ูŠู‚ูŠูŠู†" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who may discover real JIDs?" msgstr "ู…ู† ูŠู…ูƒู†ู‡ู… ุงุณุชูƒุดุงู ู…ูุนุฑู‘ููŠ JID ุงู„ุญู‚ูŠู‚ูŠูŠู†ุŸ" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 msgid "Password" msgstr "ูƒู„ู…ุฉ ุงู„ุณุฑ" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "Password required for room entry, if any" msgstr "ู…ุทู„ูˆุจ ูƒู„ู…ุฉ ุงู„ุณุฑ ู„ุฏุฎูˆู„ ุงู„ุบุฑูุฉุŒ ุฅู† ูˆูุฌูุฏุช" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "ุชุญุช ุงู„ุฅุดุฑุงู" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "ุฅู„ุง ุงู„ู…ุดุงุฑูƒูˆู† ุงู„ุฐูŠู† ูŠุชู…ุชุนูˆู† ุจุญู‚ ุงู„ูƒุชุงุจุฉ ูŠู…ูƒู†ู‡ู… ุงุฑุณุงู„ ุงู„ุฑุณุงุฆู„" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "ุงู„ุฃุนุถุงุก ูู‚ุท" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "ูŠู…ูƒู† ู„ู„ุฃุนุถุงุก ูู‚ุท ุงู„ุฅู„ุชุญุงู‚ ุจุงู„ุบุฑูุฉ" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "ุฅุฏุงุฑุฉ ุงู„ุชุฃุฑูŠุฎ" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "ุงู„ุญุฏ ุงู„ุฃู‚ุตู‰ ู„ู„ุณุฌูู„ ุงู„ู…ุคุฑู‘ูŽุฎ ุงู„ุตุงุฏุฑ ุนู† ูุฑูŠู‚ ุงู„ู…ุญุงุฏุซุฉ" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "ุฅุนุฏุงุฏุงุช ุงู„ุบุฑูุฉ" #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/src/ui/contact_details/settings_provider.vala:12 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "ุงู„ุฅุนุฏุงุฏุงุช" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "ุญุธุฑ" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "ุชู… ุญุธุฑ ุงู„ุฅุชุตุงู„ ูˆุชุญุฏูŠุซุงุช ุงู„ุญุงู„ุฉ ููŠ ูƒู„ุง ุงู„ุฅุชุฌุงู‡ูŠู†" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "ุงู„ุฅุนุฏุงุฏุงุช ุงู„ู…ุญู„ูŠุฉ" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "ุฅุฑุณุงู„ ุฅุดุนุงุฑุงุช ุนู†ุฏ ุงู„ูƒุชุงุจุฉ" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "ุฅุฑุณุงู„ ุฅูŠุตุงู„ุงุช ุงู„ู‚ุฑุงุกุฉ" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "ุงู„ุฅุดุนุงุฑุงุช" #: main/src/ui/contact_details/settings_provider.vala:82 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:124 msgid "On" msgstr "ู†ุดุท" #: main/src/ui/contact_details/settings_provider.vala:84 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "Off" msgstr "ู…ุนุทู‘ู„" #: main/src/ui/contact_details/settings_provider.vala:86 msgid "Only when mentioned" msgstr "ูู‚ุท ุนู†ุฏู…ุง ูŠูุดุงุฑ ุฅู„ูŠู‘" #: main/src/ui/contact_details/settings_provider.vala:88 #: main/src/ui/contact_details/settings_provider.vala:123 #, c-format msgid "Default: %s" msgstr "ุฅูุชุฑุงุถูŠ: %s" #: main/src/ui/conversation_titlebar/search_entry.vala:11 msgid "Search messages" msgstr "ุงู„ุจุญุซ ุนู† ุฑุณุงุฆู„" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "ุงู„ุฃุนุถุงุก" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "ุงู„ุฅุดุนุงุฑ ุนู†ุฏ ุชู„ู‚ูŠ ุฑุณุงุฆู„ ุฌุฏูŠุฏุฉ" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "ุชุญูˆูŠู„ ุงู„ูˆุฌูˆู‡ ุงู„ู…ุจุชุณู…ุฉ ุฅู„ู‰ ุฅูŠู…ูˆุฌูŠ" #: main/data/manage_accounts/dialog.ui:9 main/data/menu_app.ui:7 msgid "Accounts" msgstr "ุงู„ุญุณุงุจุงุช" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "ุงู„ู„ู‚ุจ ุงู„ู…ุญู„ูŠ" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "ู„ู… ูŠุชู… ุฅุนุฏุงุฏ ุฃูŠ ุญุณุงุจ ุจุนุฏ" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "ุฅุถุงูุฉ ุญุณุงุจ" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "ุฅู†ุดุงุก ุญุณุงุจ" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "ุฅู†ุดุงุก ุญุณุงุจ" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "ู„ุง ูŠู…ูƒู† ุฅู†ุดุงุก ุงุชุตุงู„ ุขู…ู†" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "ุงุชุตู„" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "ุฅุฎุชุฑ ุฎุงุฏู…ู‹ุง ุนู…ูˆู…ูŠู‹ุง" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "ุฃูˆ ู‚ู… ุจุฅุฏุฎุงู„ ุนู†ูˆุงู† ุฎุงุฏู…" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "ู‚ู… ุจุชุณุฌูŠู„ ุงู„ุฏุฎูˆู„ ุจุฏู„ุงู‹ ู…ู† ุฐู„ูƒ" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "ุฅุฎุชูŠุงุฑ ุณูŠุฑูุฑ ุขุฎูŽุฑ" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "ู„ู‚ุฏ ุฃุนุฏุฏุช ูƒู„ ุดูŠุก!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "ุฃู†ู‡ูŠ" #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "ุบูŠุฑ ู…ุดูู‘ุฑุฉ" #: main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "ุฎุชุตุงุฑุงุช ู„ูˆุญุฉ ุงู„ู…ูุงุชูŠุญ" #: main/data/menu_app.ui:21 msgid "About Dino" msgstr "ุนู† ุฏูŠู†ูˆ" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "ุชุทุจูŠู‚ ุญุฏูŠุซ ู„ู„ุฏุฑุฏุดุฉ ุนุจุฑ XMPP" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "ุฏูŠู†ูˆ ุจุฑู†ุงู…ุฌ ุญุฏูŠุซ ูˆู…ูุชูˆุญ ุงู„ู…ุตุฏุฑ ู„ู„ุฏุฑุฏุดุฉ ุตูู…ู‘ู… ู„ุณุทุญ ุงู„ู…ูƒุชุจ. ูˆูŠูุฑูƒู‘ุฒ ุนู„ูŠ ุชู‚ุฏูŠู… " "ุชุฌุฑุจุฉ ู†ุธูŠูุฉ ูˆู…ูˆุซูˆู‚ ู…ู†ู‡ุง ู„ุฌุงุจุฑ/XMPP ู…ุน ุฃุฎุฐ ุฎุตูˆุตูŠุชูƒู… ุจุนูŠู† ุงู„ุฅุนุชุจุงุฑ." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "ูˆู‡ูˆ ูŠุฏุนู… ุงู„ุชุดููŠุฑ ุจูˆุงุณุทุฉ OMEMO ูˆ OpenPGP ูŠุณู…ุญ ุจุฅุนุฏุงุฏ ู…ูŠุฒุงุช ุงู„ุฎุตูˆุตูŠุฉ " "ูƒุงู„ุฅูŠุตุงู„ุงุช ุงู„ู…ู‚ุฑูˆุกุฉ ูˆุงู„ุฅุฎุทุงุฑุงุช ุนู†ุฏ ุงู„ูƒุชุงุจุฉ." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "ูŠู‚ูˆู… ุฏูŠู†ูˆ ุจุฌู„ุจ ุงู„ุณูุฌู„ู‘ ู…ูู† ุงู„ุณูŠุฑูุฑ ุซู… ูŠูุฒุงู…ูู† ุงู„ุฑุณุงุฆู„ ู…ุน ุงู„ุฃุฌู‡ุฒุฉ ุงู„ุฃุฎุฑู‰." #: main/data/im.dino.Dino.appdata.xml.in:16 msgid "Main window with conversations" msgstr "ุงู„ู†ุงูุฐุฉ ุงู„ุฑุฆูŠุณูŠุฉ ุจุงู„ู…ูุญุงุฏุซุงุช" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "ุงู„ุญุณุงุจ" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "ุงู„ุฅุณู… ุงู„ู…ุณุชุนุงุฑ" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "ุงู„ู„ู‚ุจ" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "ุฅุถุงูุฉ ู…ูุฑุงุณูู„" #: main/data/shortcuts.ui:12 msgid "General" msgstr "ุนุงู…" #: main/data/shortcuts.ui:32 msgid "Navigation" msgstr "ุงู„ุฅุจุญุงุฑ" #: main/data/shortcuts.ui:37 msgid "Jump to next conversation" msgstr "ุงู„ุฅู†ุชู‚ุงู„ ุฅู„ู‰ ุงู„ู…ุญุงุฏุซุฉ ุงู„ุชุงู„ูŠุฉ" #: main/data/shortcuts.ui:44 msgid "Jump to previous conversation" msgstr "ุงู„ุฅู†ุชู‚ุงู„ ุฅู„ู‰ ุงู„ู…ุญุงุฏุซุฉ ุงู„ุณุงุจู‚ุฉ" #: main/data/global_search.ui:37 msgid "No active search" msgstr "ู„ุง ูŠูˆุฌุฏ ุจุญุซ ู†ุดุท" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "ุฃูƒุชุจ ู„ู„ุดุฑูˆุน ููŠ ุงู„ุจุญุซ" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "ู„ุง ุชูˆุฌุฏ ุฑุณุงุฆู„ ู…ุชุทุงุจู‚ุฉ" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "ุชุญู‚ู‚ ู…ูู† ุงู„ุชุฏู‚ูŠู‚ ุงู„ุฅู…ู„ุงุฆูŠ ุฃูˆ ุญุงูˆู„ ุฅุฒุงู„ุฉ ูู„ุงุชุฑ" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "ุงุถุบุท ู‡ู†ุง ู„ุจุฏุงูŠุฉ ุงู„ู…ุญุงุฏุซุฉ ุฃูˆ ู„ู„ุฅู†ุถู…ุงู… ุฅู„ู‰ ู‚ู†ุงุฉ." #: main/data/unified_main_content.ui:147 msgid "You have no open chats" msgstr "ู„ูŠุณ ู„ุฏูŠูƒ ุฃูŠุฉ ู…ุญุงุฏุซุฉ ู…ูุชูˆุญุฉ" #~ msgid "Failed connecting to %s" #~ msgstr "ูุดู„ ุงู„ุงุชุตุงู„ ุจู€ %s" #~ msgid "Join Conference" #~ msgstr "ุงู„ุฅู†ุถู…ุงู… ุฅู„ู‰ ูุฑูŠู‚ ู…ุญุงุฏุซุฉ" #~ msgid "File" #~ msgstr "ู…ู„ู" #~ msgid "Communicate happiness." #~ msgstr "ุฃู†ุดุฑ ุงู„ุณุนุงุฏุฉ." #~ msgid "Preferences" #~ msgstr "ุงู„ุชูุถูŠู„ุงุช" #~ msgid "Quit" #~ msgstr "ุงู„ุฎุฑูˆุฌ" #~ msgid "JID should be of the form โ€œuser@example.comโ€" #~ msgstr "ูŠุฌุจ ุฃู† ูŠูƒูˆู† JID ุนู„ู‰ ุดูƒู„ โ€œuser@example.comโ€" #~ msgid "Copy Link Address" #~ msgstr "ู†ุณุฎ ุงู„ุนู†ูˆุงู†" #~ msgid "Copy" #~ msgstr "ู†ุณุฎ" #~ msgid "Select All" #~ msgstr "ุงุฎุชูŠุงุฑ ุงู„ูƒู„" #~ msgid "Search" #~ msgstr "ุงู„ุจุญุซ" #~ msgid "Start Chat" #~ msgstr "ุฅุจุฏุฃ ุงู„ุฏุฑุฏุดุฉ" dino-0.1.0/main/po/ca.po0000644000000000000000000006010413614354364013436 0ustar rootroot# Catalan translation for Dino. # This file is distributed under the same license as the dino package. # Jordi Mallach , 2018-2019. # msgid "" msgstr "" "Project-Id-Version: dino 20180510\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2020-01-16 00:21+0000\n" "Language-Team: Catalan; Valencian \n" "Language: ca\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.11-dev\n" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "Image sent" msgstr "Imatge enviada" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "File sent" msgstr "Fitxer enviat" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "Image received" msgstr "Imatge rebuda" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "File received" msgstr "Fitxer rebut" #: main/src/ui/notifications.vala:98 msgid "Subscription request" msgstr "Peticiรณ de subscripciรณ" #: main/src/ui/notifications.vala:105 main/src/ui/notifications.vala:140 #: main/src/ui/conversation_summary/subscription_notification.vala:37 msgid "Accept" msgstr "Acepta" #: main/src/ui/notifications.vala:106 main/src/ui/notifications.vala:139 #: main/src/ui/conversation_summary/subscription_notification.vala:38 msgid "Deny" msgstr "Rebutja" #: main/src/ui/notifications.vala:112 #: main/src/ui/manage_accounts/add_account_dialog.vala:258 #: main/src/ui/add_conversation/conference_details_fragment.vala:188 #, c-format msgid "Could not connect to %s" msgstr "No s'ha pogut connectar a %s" #: main/src/ui/notifications.vala:128 #, c-format msgid "Invitation to %s" msgstr "Invitaciรณ a %s" #: main/src/ui/notifications.vala:129 #, c-format msgid "%s invited you to %s" msgstr "%s us ha convidat a %s" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Voleu suprimir el compte %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Suprimeix" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Seleccioneu l'ร vatar" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Selecciona" #: main/src/ui/manage_accounts/dialog.vala:152 main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Cancelยทla" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Imatges" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Tots els fitxers" #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connectingโ€ฆ" msgstr "S'estร  connectantโ€ฆ" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Connectat" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Desconnectat" #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Contrasenya incorrecta" #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "El certificat TLS รฉs invร lid" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Error" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Afegeix un compte" #: main/src/ui/manage_accounts/add_account_dialog.vala:166 #, c-format msgid "Sign in to %s" msgstr "Entra a %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:210 #, c-format msgid "You can now start using %s" msgstr "Ara podeu comenรงar a emprar %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:263 #: main/src/ui/manage_accounts/add_account_dialog.vala:296 #: main/src/ui/manage_accounts/add_account_dialog.vala:306 #: main/src/ui/manage_accounts/add_account_dialog.vala:382 #: main/src/ui/add_conversation/conference_details_fragment.vala:191 msgid "Invalid address" msgstr "L'adreรงa no รฉs vร lida" #: main/src/ui/manage_accounts/add_account_dialog.vala:278 msgid "Wrong username or password" msgstr "Usuari o contrasenya incorrecta" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Something went wrong" msgstr "Ha fallat alguna cosa" #: main/src/ui/manage_accounts/add_account_dialog.vala:321 msgid "No response from server" msgstr "No s'ha rebut una resposta del servidor" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 #, c-format msgid "Register on %s" msgstr "Registre en %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:330 msgid "The server requires to sign up through a website" msgstr "El servidor requereix un registre a travรฉs d'una pร gina web" #: main/src/ui/manage_accounts/add_account_dialog.vala:332 msgid "Open Registration" msgstr "Registre obert" #: main/src/ui/manage_accounts/add_account_dialog.vala:344 msgid "Register" msgstr "Registre" #: main/src/ui/manage_accounts/add_account_dialog.vala:346 #, c-format msgid "Check %s for information on how to sign up" msgstr "Mireu %s per obtenir informaciรณ sobre com registrar-vos" #: main/src/ui/conversation_selector/conversation_selector_row.vala:146 #: main/src/ui/conversation_selector/conversation_selector_row.vala:160 #: main/src/ui/util/helper.vala:136 main/src/ui/util/helper.vala:151 msgid "Me" msgstr "Jo" #: main/src/ui/conversation_selector/conversation_selector_row.vala:258 #: main/src/ui/conversation_summary/date_separator_populator.vala:100 #, no-c-format msgid "%b %d" msgstr "%b %d" #: main/src/ui/conversation_selector/conversation_selector_row.vala:262 #: main/src/ui/conversation_summary/date_separator_populator.vala:91 msgid "Yesterday" msgstr "Ahir" #: main/src/ui/conversation_selector/conversation_selector_row.vala:265 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:205 #, no-c-format msgid "%Hโˆถ%M" msgstr "%H.%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:266 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:206 #, no-c-format msgid "%lโˆถ%M %p" msgstr "%l.%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:269 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:210 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "fa %i minut" msgstr[1] "fa %i minuts" #: main/src/ui/conversation_selector/conversation_selector_row.vala:271 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:212 msgid "Just now" msgstr "Ara mateix" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/unified_window.vala:249 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Uneix-m'hi al canal" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 msgid "Join" msgstr "Uneix-te" #: main/src/ui/util/helper.vala:126 #, c-format msgid "%s from %s" msgstr "%s de %s" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:28 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Afegeix" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:42 msgid "Save" msgstr "Desa" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Segรผent" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Enrere" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/src/ui/unified_window.vala:248 #: main/data/conversation_list_titlebar_csd.ui:12 #: main/data/conversation_list_titlebar.ui:16 main/data/menu_add.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:20 main/data/shortcuts.ui:17 msgid "Start Conversation" msgstr "Inicia una conversa" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Inicia" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joiningโ€ฆ" msgstr "S'estร  unintโ€ฆ" #: main/src/ui/add_conversation/conference_details_fragment.vala:169 msgid "Password required to enter room" msgstr "Es requereix una contrasenya per entrar a la sala" #: main/src/ui/add_conversation/conference_details_fragment.vala:174 msgid "Banned from joining or creating conference" msgstr "Heu estat bandejat de unir-vos o crear conferรจncies" #: main/src/ui/add_conversation/conference_details_fragment.vala:176 msgid "Room does not exist" msgstr "La sala no existeix" #: main/src/ui/add_conversation/conference_details_fragment.vala:178 msgid "Not allowed to create room" msgstr "No esteu autoritzat a crear una sala" #: main/src/ui/add_conversation/conference_details_fragment.vala:180 msgid "Members-only room" msgstr "Sala nomรฉs per membres" #: main/src/ui/add_conversation/conference_details_fragment.vala:183 msgid "Choose a different nick" msgstr "Seleccioneu un sobrenom diferent" #: main/src/ui/add_conversation/conference_details_fragment.vala:185 msgid "Too many occupants in room" msgstr "Massa ocupants a la sala" #: main/src/ui/unified_window.vala:227 msgid "Welcome to Dino!" msgstr "Us donem la benvinguda al Dino!" #: main/src/ui/unified_window.vala:228 msgid "Sign in or create an account to get started." msgstr "Entreu o creeu un compte per a comenรงar." #: main/src/ui/unified_window.vala:229 msgid "Set up account" msgstr "Configureu un compte" #: main/src/ui/unified_window.vala:237 msgid "No active accounts" msgstr "No hi ha cap compte actiu" #: main/src/ui/unified_window.vala:238 msgid "Manage accounts" msgstr "Gestiona els comptes" #: main/src/ui/unified_window.vala:247 msgid "No active conversations" msgstr "Cap conversa activa" #: main/src/ui/conversation_summary/file_widget.vala:275 #, c-format msgid "Downloading %sโ€ฆ" msgstr "S'estร  baixant %sโ€ฆ" #: main/src/ui/conversation_summary/file_widget.vala:281 #, c-format msgid "%s offered: %s" msgstr "%s ha oferit: %s" #: main/src/ui/conversation_summary/file_widget.vala:283 #, c-format msgid "File offered: %s" msgstr "Fitxer oferit: %s" #: main/src/ui/conversation_summary/file_widget.vala:285 msgid "File offered" msgstr "Fitxer oferit" #: main/src/ui/conversation_summary/file_widget.vala:290 msgid "File transfer failed" msgstr "Ha fallat la transferรจncia del fitxer" #: main/src/ui/conversation_summary/date_separator_populator.vala:85 msgid "Today" msgstr "Avui" #: main/src/ui/conversation_summary/date_separator_populator.vala:98 msgid "%a, %b %d" msgstr "%a, %b %d" #: main/src/ui/conversation_summary/chat_state_populator.vala:145 #, c-format msgid "%s, %s and %i others" msgstr "%s, %s i %i altres" #: main/src/ui/conversation_summary/chat_state_populator.vala:147 #, c-format msgid "%s, %s and %s" msgstr "%s, %s i %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:149 #, c-format msgid "%s and %s" msgstr "%s i %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:154 msgid "is typingโ€ฆ" msgid_plural "are typingโ€ฆ" msgstr[0] "estร  escrivintโ€ฆ" msgstr[1] "estan escrivintโ€ฆ" #: main/src/ui/conversation_summary/chat_state_populator.vala:156 msgid "has stopped typing" msgstr "ha parat d'escriure" #: main/src/ui/conversation_summary/content_item_widget_factory.vala:59 msgid "Message too long" msgstr "El missatge รฉs massa llarg" #: main/src/ui/conversation_summary/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Aquest contacte vol afegir-vos a la seva llista de contactes" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:193 #, no-c-format msgid "%x, %Hโˆถ%M" msgstr "%x, %H.%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:194 #, no-c-format msgid "%x, %lโˆถ%M %p" msgstr "%x, %l.%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:197 #, no-c-format msgid "%b %d, %Hโˆถ%M" msgstr "%b %d, %H.%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:198 #, no-c-format msgid "%b %d, %lโˆถ%M %p" msgstr "%b %d, %l.%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:201 #, no-c-format msgid "%a, %Hโˆถ%M" msgstr "%a, %H.%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:202 #, no-c-format msgid "%a, %lโˆถ%M %p" msgstr "%a, %l.%M %p" #: main/src/ui/occupant_menu/list.vala:102 msgid "Owner" msgstr "Propietari" #: main/src/ui/occupant_menu/list.vala:104 msgid "Admin" msgstr "Administrador" #: main/src/ui/occupant_menu/list.vala:106 msgid "Member" msgstr "Membre" #: main/src/ui/occupant_menu/list.vala:108 msgid "User" msgstr "Usuari" #: main/src/ui/occupant_menu/view.vala:29 #: main/src/ui/occupant_menu/view.vala:38 msgid "Invite" msgstr "Invita" #: main/src/ui/occupant_menu/view.vala:37 msgid "Invite to Conference" msgstr "Invita a la conferรจncia" #: main/src/ui/occupant_menu/view.vala:88 msgid "Start private conversation" msgstr "Inicia una convesa privada" #: main/src/ui/occupant_menu/view.vala:96 msgid "Kick" msgstr "Expulsa" #: main/src/ui/global_search.vala:140 #, c-format msgid "%i search results" msgstr "%i resultats de cerca" #: main/src/ui/global_search.vala:167 #, c-format msgid "In %s" msgstr "En %s" #: main/src/ui/global_search.vala:167 #, c-format msgid "With %s" msgstr "Amb %s" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Detalls de la conferรจncia" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:24 msgid "Contact Details" msgstr "Detalls del contacte" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Nom de la sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Descripciรณ de la sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Persistent" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "La sala romandrร  desprรฉs de la sortida del l'รบltim ocupant" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Cercable pรบblicament" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Els ocupants poden canviar el tema" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Discover real JIDs" msgstr "Descobreix els JID reals" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who may discover real JIDs?" msgstr "Qui pot descobrir els JID reals?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 msgid "Password" msgstr "Contrasenya" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "Password required for room entry, if any" msgstr "La contrasenya requerida per entrar a la sala, si cal" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderat" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Nomรฉs els ocupants amb veu poden enviar missatges" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Nomรฉs per membres" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Nomรฉs els membres poden entrar a la sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Historial dels missatges" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Quantitat mร xima de l'historial proporcionat per la sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Configuraciรณ de la sala" #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/src/ui/contact_details/settings_provider.vala:12 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Preferรจncies" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Bloca" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "" "La comunicaciรณ i actualitzacions d'estat en ambdรบes direccions estan blocades" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Preferรจncies locals" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Envia notificacions de tecleig" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Envia rebuts de lectura" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Notificacions" #: main/src/ui/contact_details/settings_provider.vala:82 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:124 msgid "On" msgstr "Habilitat" #: main/src/ui/contact_details/settings_provider.vala:84 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "Off" msgstr "Inhabilitat" #: main/src/ui/contact_details/settings_provider.vala:86 msgid "Only when mentioned" msgstr "Nomรฉs quan rebeu una menciรณ" #: main/src/ui/contact_details/settings_provider.vala:88 #: main/src/ui/contact_details/settings_provider.vala:123 #, c-format msgid "Default: %s" msgstr "Per defecte: %s" #: main/src/ui/conversation_titlebar/search_entry.vala:11 msgid "Search messages" msgstr "Cerca els missatges" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Membres" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Notifica'm quan arriba un missatge nou" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Converteix ยซsmileysยป a emoji" #: main/data/manage_accounts/dialog.ui:9 main/data/menu_app.ui:7 msgid "Accounts" msgstr "Comptes" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "ร€lias local" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "No hi ha cap compte configurat" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Afegeix un compte" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Entra" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Crea un compte" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "No s'ha pogut una connexiรณ segura" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Connecta" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Trieu un servidor pรบblic" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "O especifiqueu l'adreรงa d'un servidor" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Entra" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Trieu un altre servidor" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Tot llest!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Finalitza" #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Sense xifrar" #: main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Dreceres de teclat" #: main/data/menu_app.ui:21 msgid "About Dino" msgstr "Quant al Dino" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Client de xat XMPP modern" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino รฉs un client de xat lliure i modern per a l'escriptori. Estร  centrat en " "proveir una experiรจncia neta i fiable de Jabber/XMPP, sempre tenint en " "compte la vostra privacitat." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Implementa xifratge punt a punt amb OMEMO i OpenPGP, i permet configurar " "funcionalitats relacionades amb la privacitat com per exemple rebuts de " "lectura i notificacions d'escriptura." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino recupera l'historial del servidor i sincronitza els missatges amb " "altres dispositius." #: main/data/im.dino.Dino.appdata.xml.in:16 msgid "Main window with conversations" msgstr "Finestra principal amb converses" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Compte" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Sobrenom" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "ร€lias" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Afegeix el contacte" #: main/data/shortcuts.ui:12 msgid "General" msgstr "General" #: main/data/shortcuts.ui:32 msgid "Navigation" msgstr "Navegaciรณ" #: main/data/shortcuts.ui:37 msgid "Jump to next conversation" msgstr "Salta a la conversa segรผent" #: main/data/shortcuts.ui:44 msgid "Jump to previous conversation" msgstr "Salta a la conversa anterior" #: main/data/global_search.ui:37 msgid "No active search" msgstr "No hi ha cap cerca activa" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Teclegeu per a comenรงar una cerca" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "No hi ha missatges coincidents" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Comproveu l'ortografia o proveu de suprimir filtres" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "Feu clic aquรญ per a comenรงar una conversa o unir-vos a un canal." #: main/data/unified_main_content.ui:147 msgid "You have no open chats" msgstr "No teniu cap xat obert" #~ msgid "Failed connecting to %s" #~ msgstr "No s'ha pogut connectar a %s" #~ msgid "Join Conference" #~ msgstr "Uneix-te a la conferรจncia" #~ msgid "File" #~ msgstr "Fitxer" #~ msgid "Preferences" #~ msgstr "Preferรจncies" #~ msgid "Quit" #~ msgstr "Surt" #~ msgid "Copy Link Address" #~ msgstr "Copia l'adreรงa de l'enllaรง" #~ msgid "Copy" #~ msgstr "Copia" #~ msgid "Select All" #~ msgstr "Selecciona-ho tot" #~ msgid "Search" #~ msgstr "Cerca" #~ msgid "Send message marker" #~ msgstr "Envia marcador de missatge" #~ msgid "Start Chat" #~ msgstr "Inicia un xat" dino-0.1.0/main/po/de.po0000644000000000000000000006047713614354364013460 0ustar rootrootmsgid "" msgstr "" "Project-Id-Version: dino-0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2019-12-26 23:21+0000\n" "Language-Team: German \n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.10\n" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "Image sent" msgstr "Bild gesendet" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "File sent" msgstr "Datei gesendet" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "Image received" msgstr "Bild empfangen" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "File received" msgstr "Datei empfangen" #: main/src/ui/notifications.vala:98 msgid "Subscription request" msgstr "Kontaktanfrage" #: main/src/ui/notifications.vala:105 main/src/ui/notifications.vala:140 #: main/src/ui/conversation_summary/subscription_notification.vala:37 msgid "Accept" msgstr "Annehmen" #: main/src/ui/notifications.vala:106 main/src/ui/notifications.vala:139 #: main/src/ui/conversation_summary/subscription_notification.vala:38 msgid "Deny" msgstr "Ablehnen" #: main/src/ui/notifications.vala:112 #: main/src/ui/manage_accounts/add_account_dialog.vala:258 #: main/src/ui/add_conversation/conference_details_fragment.vala:188 #, c-format msgid "Could not connect to %s" msgstr "Verbindung zu %s konnte nicht hergestellt werden" #: main/src/ui/notifications.vala:128 #, c-format msgid "Invitation to %s" msgstr "Einladung zu %s" #: main/src/ui/notifications.vala:129 #, c-format msgid "%s invited you to %s" msgstr "%s hat dich in %s eingeladen" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Konto %s lรถschen?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Lรถschen" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Kontaktbild auswรคhlen" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Auswรคhlen" #: main/src/ui/manage_accounts/dialog.vala:152 main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Abbrechen" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Bilder" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Alle Dateien" #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connectingโ€ฆ" msgstr "Verbindenโ€ฆ" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Verbunden" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Nicht verbunden" #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Passwort falsch" #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Ungรผltiges TLS-Zertifikat" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Fehler" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Konto hinzufรผgen" #: main/src/ui/manage_accounts/add_account_dialog.vala:166 #, c-format msgid "Sign in to %s" msgstr "Bei %s einloggen" #: main/src/ui/manage_accounts/add_account_dialog.vala:210 #, c-format msgid "You can now start using %s" msgstr "Du kannst %s ab jetzt nutzen" #: main/src/ui/manage_accounts/add_account_dialog.vala:263 #: main/src/ui/manage_accounts/add_account_dialog.vala:296 #: main/src/ui/manage_accounts/add_account_dialog.vala:306 #: main/src/ui/manage_accounts/add_account_dialog.vala:382 #: main/src/ui/add_conversation/conference_details_fragment.vala:191 msgid "Invalid address" msgstr "Ungรผltige Adresse" #: main/src/ui/manage_accounts/add_account_dialog.vala:278 msgid "Wrong username or password" msgstr "Benutzername oder Passwort falsch" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Something went wrong" msgstr "Etwas ist schief gelaufen" #: main/src/ui/manage_accounts/add_account_dialog.vala:321 msgid "No response from server" msgstr "Keine Antwort vom Server" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 #, c-format msgid "Register on %s" msgstr "Konto auf %s erstellen" #: main/src/ui/manage_accounts/add_account_dialog.vala:330 msgid "The server requires to sign up through a website" msgstr "Der Server erfordert die Registrierung durch eine Webseite" #: main/src/ui/manage_accounts/add_account_dialog.vala:332 msgid "Open Registration" msgstr "Registrierung รถffnen" #: main/src/ui/manage_accounts/add_account_dialog.vala:344 msgid "Register" msgstr "Registrieren" #: main/src/ui/manage_accounts/add_account_dialog.vala:346 #, c-format msgid "Check %s for information on how to sign up" msgstr "Eventuell sind auf %s Informationen zur Registrierung zu finden" #: main/src/ui/conversation_selector/conversation_selector_row.vala:146 #: main/src/ui/conversation_selector/conversation_selector_row.vala:160 #: main/src/ui/util/helper.vala:136 main/src/ui/util/helper.vala:151 msgid "Me" msgstr "Ich" #: main/src/ui/conversation_selector/conversation_selector_row.vala:258 #: main/src/ui/conversation_summary/date_separator_populator.vala:100 #, no-c-format msgid "%b %d" msgstr "%d. %b" #: main/src/ui/conversation_selector/conversation_selector_row.vala:262 #: main/src/ui/conversation_summary/date_separator_populator.vala:91 msgid "Yesterday" msgstr "Gestern" #: main/src/ui/conversation_selector/conversation_selector_row.vala:265 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:205 #, no-c-format msgid "%Hโˆถ%M" msgstr "%Hโˆถ%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:266 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:206 #, no-c-format msgid "%lโˆถ%M %p" msgstr "%lโˆถ%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:269 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:210 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "vor %i min" msgstr[1] "vor %i min" #: main/src/ui/conversation_selector/conversation_selector_row.vala:271 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:212 msgid "Just now" msgstr "Gerade eben" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/unified_window.vala:249 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Kanal beitreten" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 msgid "Join" msgstr "Beitreten" #: main/src/ui/util/helper.vala:126 #, c-format msgid "%s from %s" msgstr "%s aus %s" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:28 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Hinzufรผgen" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:42 msgid "Save" msgstr "Speichern" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Weiter" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Zurรผck" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/src/ui/unified_window.vala:248 #: main/data/conversation_list_titlebar_csd.ui:12 #: main/data/conversation_list_titlebar.ui:16 main/data/menu_add.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:20 main/data/shortcuts.ui:17 msgid "Start Conversation" msgstr "Unterhaltung starten" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Starten" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joiningโ€ฆ" msgstr "Beitretenโ€ฆ" #: main/src/ui/add_conversation/conference_details_fragment.vala:169 msgid "Password required to enter room" msgstr "Raum erfordert Passwort" #: main/src/ui/add_conversation/conference_details_fragment.vala:174 msgid "Banned from joining or creating conference" msgstr "Beitreten oder erstellen der Konferenz verboten" #: main/src/ui/add_conversation/conference_details_fragment.vala:176 msgid "Room does not exist" msgstr "Raum existiert nicht" #: main/src/ui/add_conversation/conference_details_fragment.vala:178 msgid "Not allowed to create room" msgstr "Raum erzeugen verboten" #: main/src/ui/add_conversation/conference_details_fragment.vala:180 msgid "Members-only room" msgstr "Raum nur fรผr Mitglieder" #: main/src/ui/add_conversation/conference_details_fragment.vala:183 msgid "Choose a different nick" msgstr "Wรคhle einen anderen Spitznamen" #: main/src/ui/add_conversation/conference_details_fragment.vala:185 msgid "Too many occupants in room" msgstr "Zu viele Nutzer im Raum" #: main/src/ui/unified_window.vala:227 msgid "Welcome to Dino!" msgstr "Willkommen bei Dino!" #: main/src/ui/unified_window.vala:228 msgid "Sign in or create an account to get started." msgstr "Melde dich an oder erstelle ein Konto, um loszulegen." #: main/src/ui/unified_window.vala:229 msgid "Set up account" msgstr "Konto einrichten" #: main/src/ui/unified_window.vala:237 msgid "No active accounts" msgstr "Keine Konten aktiv" #: main/src/ui/unified_window.vala:238 msgid "Manage accounts" msgstr "Konten verwalten" #: main/src/ui/unified_window.vala:247 msgid "No active conversations" msgstr "Keine Unterhaltung aktiv" #: main/src/ui/conversation_summary/file_widget.vala:275 #, c-format msgid "Downloading %sโ€ฆ" msgstr "Lade %s herunterโ€ฆ" #: main/src/ui/conversation_summary/file_widget.vala:281 #, c-format msgid "%s offered: %s" msgstr "%s angeboten: %s" #: main/src/ui/conversation_summary/file_widget.vala:283 #, c-format msgid "File offered: %s" msgstr "Datei angeboten: %s" #: main/src/ui/conversation_summary/file_widget.vala:285 msgid "File offered" msgstr "Datei angeboten" #: main/src/ui/conversation_summary/file_widget.vala:290 msgid "File transfer failed" msgstr "Dateiรผbertragung fehlgeschlagen" #: main/src/ui/conversation_summary/date_separator_populator.vala:85 msgid "Today" msgstr "Heute" #: main/src/ui/conversation_summary/date_separator_populator.vala:98 msgid "%a, %b %d" msgstr "%a, %d %b" #: main/src/ui/conversation_summary/chat_state_populator.vala:145 #, c-format msgid "%s, %s and %i others" msgstr "%s, %s und %i weitere" #: main/src/ui/conversation_summary/chat_state_populator.vala:147 #, c-format msgid "%s, %s and %s" msgstr "%s, %s und %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:149 #, c-format msgid "%s and %s" msgstr "%s und %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:154 msgid "is typingโ€ฆ" msgid_plural "are typingโ€ฆ" msgstr[0] "tippt geradeโ€ฆ" msgstr[1] "tippen geradeโ€ฆ" #: main/src/ui/conversation_summary/chat_state_populator.vala:156 msgid "has stopped typing" msgstr "hat aufgehรถrt zu tippen" #: main/src/ui/conversation_summary/content_item_widget_factory.vala:59 msgid "Message too long" msgstr "Nachricht zu lang" #: main/src/ui/conversation_summary/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Dieser Kontakt will dich zu seiner Kontaktliste hinzufรผgen" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:193 #, no-c-format msgid "%x, %Hโˆถ%M" msgstr "%x, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:194 #, no-c-format msgid "%x, %lโˆถ%M %p" msgstr "%x, %lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:197 #, no-c-format msgid "%b %d, %Hโˆถ%M" msgstr "%d. %b, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:198 #, no-c-format msgid "%b %d, %lโˆถ%M %p" msgstr "%d. %b, %lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:201 #, no-c-format msgid "%a, %Hโˆถ%M" msgstr "%a, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:202 #, no-c-format msgid "%a, %lโˆถ%M %p" msgstr "%a, %lโˆถ%M %p" #: main/src/ui/occupant_menu/list.vala:102 msgid "Owner" msgstr "Eigentรผmer" #: main/src/ui/occupant_menu/list.vala:104 msgid "Admin" msgstr "Administrator" #: main/src/ui/occupant_menu/list.vala:106 msgid "Member" msgstr "Mitglied" #: main/src/ui/occupant_menu/list.vala:108 msgid "User" msgstr "Gast" #: main/src/ui/occupant_menu/view.vala:29 #: main/src/ui/occupant_menu/view.vala:38 msgid "Invite" msgstr "Einladen" #: main/src/ui/occupant_menu/view.vala:37 msgid "Invite to Conference" msgstr "Zur Konferenz einladen" #: main/src/ui/occupant_menu/view.vala:88 msgid "Start private conversation" msgstr "Private Unterhaltung beginnen" #: main/src/ui/occupant_menu/view.vala:96 msgid "Kick" msgstr "Hinauswerfen" #: main/src/ui/global_search.vala:140 #, c-format msgid "%i search results" msgstr "%i Suchergebnisse" #: main/src/ui/global_search.vala:167 #, c-format msgid "In %s" msgstr "In %s" #: main/src/ui/global_search.vala:167 #, c-format msgid "With %s" msgstr "Mit %s" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Konferenzdetails" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:24 msgid "Contact Details" msgstr "Kontaktdetails" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Name des Raums" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Beschreibung des Raums" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Dauerhaft" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "" "Der Raum bleibt erhalten, nachdem der letzte Benutzer ihn verlassen hat" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "ร–ffentlich sichtbar" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Nutzer kรถnnen das Thema รคndern" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Discover real JIDs" msgstr "Echte JIDs finden" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who may discover real JIDs?" msgstr "Wer kann echte JIDs sehen?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 msgid "Password" msgstr "Passwort" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "Password required for room entry, if any" msgstr "Passwort zum Betreten des Raums, falls gesetzt" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderiert" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Nur Nutzer mit Stimme kรถnnen Nachrichten senden" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "AusschlieรŸlich Mitglieder" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Nur Mitglieder dรผrfen den Raum betreten" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Gesprรคchsverlauf" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Anzahl der fรผr diesen Raum gespeicherten Nachrichten" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Raum Konfiguration" #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/src/ui/contact_details/settings_provider.vala:12 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Einstellungen" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Blockieren" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "" "Kommunikation und Statusaktualisierungen sind in beide Richtungen blockiert" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Lokale Einstellungen" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Tippbenachrichtigungen senden" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Lesebestรคtigungen senden" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Benachrichtigungen" #: main/src/ui/contact_details/settings_provider.vala:82 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:124 msgid "On" msgstr "An" #: main/src/ui/contact_details/settings_provider.vala:84 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "Off" msgstr "Aus" #: main/src/ui/contact_details/settings_provider.vala:86 msgid "Only when mentioned" msgstr "Nur bei Erwรคhnung" #: main/src/ui/contact_details/settings_provider.vala:88 #: main/src/ui/contact_details/settings_provider.vala:123 #, c-format msgid "Default: %s" msgstr "Standard: %s" #: main/src/ui/conversation_titlebar/search_entry.vala:11 msgid "Search messages" msgstr "Nachrichten suchen" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Mitglieder" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Benachrichtigen bei Empfang einer neuen Nachricht" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Smileys zu Emojis umwandeln" #: main/data/manage_accounts/dialog.ui:9 main/data/menu_app.ui:7 msgid "Accounts" msgstr "Konten" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Lokaler Alias" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Keine Konten konfiguriert" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Konto hinzufรผgen" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Einloggen" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Konto erstellen" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Es konnte keine sichere Verbindung hergestellt werden" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Verbinden" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Wรคhle einen รถffentlichen Server" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Oder gib eine Serveradresse an" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Stattdessen einloggen" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Anderen Server wรคhlen" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Fertig!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Fertig" #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Unverschlรผsselt" #: main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Tastenkombinationen" #: main/data/menu_app.ui:21 msgid "About Dino" msgstr "Info zu Dino" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Moderner XMPP Chat Client" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino ist ein moderner, quelloffener Chat Client. Er bietet eine aufgerรคumte " "und robuste Jabber/XMPP Erfahrung und legt einen Schwerpunkt auf " "Privatsphรคre." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Er unterstรผtzt Ende-zu-Ende Verschlรผsselung mit OMEMO und OpenPGP und " "enthรคlt Privatsphรคre-Einstellungen zu Lesebestรคtigungen und " "Tippbenachrichtigungen." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino ruft Gesprรคchsverlรคufe vom Server ab und synchronisiert Nachrichten mit " "anderen Gerรคten." #: main/data/im.dino.Dino.appdata.xml.in:16 msgid "Main window with conversations" msgstr "Hauptfenster mit Konversationen" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Konto" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Spitzname" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Alias" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Kontakt hinzufรผgen" #: main/data/shortcuts.ui:12 msgid "General" msgstr "Allgemein" #: main/data/shortcuts.ui:32 msgid "Navigation" msgstr "Navigation" #: main/data/shortcuts.ui:37 msgid "Jump to next conversation" msgstr "Zur nรคchsten Konversation springen" #: main/data/shortcuts.ui:44 msgid "Jump to previous conversation" msgstr "Zur letzten Konversation springen" #: main/data/global_search.ui:37 msgid "No active search" msgstr "Keine Suche gestartet" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Tippe um eine Suche zu beginnen" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Keine Nachrichten gefunden" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "รœberprรผfe die Schreibweise oder entferne Filter" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "" "Klicke hier, um eine Unterhaltung zu starten oder einem Kanal beizutreten." #: main/data/unified_main_content.ui:147 msgid "You have no open chats" msgstr "Du hast keine offenen Chats" #~ msgid "Failed connecting to %s" #~ msgstr "Verbindung zu %s fehlgeschlagen" #~ msgid "Join Conference" #~ msgstr "Konferenz beitreten" #~ msgid "File" #~ msgstr "Datei" #~ msgid "Communicate happiness." #~ msgstr "Glรผcklich(keit) kommunizieren." #~ msgid "Preferences" #~ msgstr "Einstellungen" #~ msgid "Quit" #~ msgstr "Beenden" #~ msgid "JID should be of the form โ€œuser@example.comโ€" #~ msgstr "JID sollte das Format โ€œbenutzer@beispiel.deโ€ haben" #~ msgid "Copy Link Address" #~ msgstr "Link-Adresse Kopieren" #~ msgid "Copy" #~ msgstr "Kopieren" #~ msgid "Select All" #~ msgstr "Alles Auswรคhlen" #~ msgid "Search" #~ msgstr "Suchen" #~ msgid "Send message marker" #~ msgstr "Lesebestรคtigung senden" #~ msgid "Start Chat" #~ msgstr "Chat starten" #~ msgid "Request presence updates" #~ msgstr "Online-Status erfragen" #~ msgid "Join on startup" #~ msgstr "Beim Start beitreten" #~ msgid "Add Chat" #~ msgstr "Chat hinzufรผgen" #~ msgid "Name" #~ msgstr "Name" #~ msgid "Description" #~ msgstr "Beschreibung" dino-0.1.0/main/po/dino.pot0000644000000000000000000004756513614354364014210 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "Image sent" msgstr "" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "File sent" msgstr "" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "Image received" msgstr "" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "File received" msgstr "" #: main/src/ui/notifications.vala:98 msgid "Subscription request" msgstr "" #: main/src/ui/notifications.vala:105 main/src/ui/notifications.vala:140 #: main/src/ui/conversation_summary/subscription_notification.vala:37 msgid "Accept" msgstr "" #: main/src/ui/notifications.vala:106 main/src/ui/notifications.vala:139 #: main/src/ui/conversation_summary/subscription_notification.vala:38 msgid "Deny" msgstr "" #: main/src/ui/notifications.vala:112 #: main/src/ui/manage_accounts/add_account_dialog.vala:258 #: main/src/ui/add_conversation/conference_details_fragment.vala:188 #, c-format msgid "Could not connect to %s" msgstr "" #: main/src/ui/notifications.vala:128 #, c-format msgid "Invitation to %s" msgstr "" #: main/src/ui/notifications.vala:129 #, c-format msgid "%s invited you to %s" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:152 main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connectingโ€ฆ" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:166 #, c-format msgid "Sign in to %s" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:210 #, c-format msgid "You can now start using %s" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:263 #: main/src/ui/manage_accounts/add_account_dialog.vala:296 #: main/src/ui/manage_accounts/add_account_dialog.vala:306 #: main/src/ui/manage_accounts/add_account_dialog.vala:382 #: main/src/ui/add_conversation/conference_details_fragment.vala:191 msgid "Invalid address" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:278 msgid "Wrong username or password" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Something went wrong" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:321 msgid "No response from server" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 #, c-format msgid "Register on %s" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:330 msgid "The server requires to sign up through a website" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:332 msgid "Open Registration" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:344 msgid "Register" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:346 #, c-format msgid "Check %s for information on how to sign up" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:146 #: main/src/ui/conversation_selector/conversation_selector_row.vala:160 #: main/src/ui/util/helper.vala:136 main/src/ui/util/helper.vala:151 msgid "Me" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:258 #: main/src/ui/conversation_summary/date_separator_populator.vala:100 #, no-c-format msgid "%b %d" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:262 #: main/src/ui/conversation_summary/date_separator_populator.vala:91 msgid "Yesterday" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:265 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:205 #, no-c-format msgid "%Hโˆถ%M" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:266 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:206 #, no-c-format msgid "%lโˆถ%M %p" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:269 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:210 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:271 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:212 msgid "Just now" msgstr "" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/unified_window.vala:249 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 msgid "Join" msgstr "" #: main/src/ui/util/helper.vala:126 #, c-format msgid "%s from %s" msgstr "" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:28 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:42 msgid "Save" msgstr "" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/src/ui/unified_window.vala:248 #: main/data/conversation_list_titlebar_csd.ui:12 #: main/data/conversation_list_titlebar.ui:16 main/data/menu_add.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:20 main/data/shortcuts.ui:17 msgid "Start Conversation" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joiningโ€ฆ" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:169 msgid "Password required to enter room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:174 msgid "Banned from joining or creating conference" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:176 msgid "Room does not exist" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:178 msgid "Not allowed to create room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:180 msgid "Members-only room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:183 msgid "Choose a different nick" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:185 msgid "Too many occupants in room" msgstr "" #: main/src/ui/unified_window.vala:227 msgid "Welcome to Dino!" msgstr "" #: main/src/ui/unified_window.vala:228 msgid "Sign in or create an account to get started." msgstr "" #: main/src/ui/unified_window.vala:229 msgid "Set up account" msgstr "" #: main/src/ui/unified_window.vala:237 msgid "No active accounts" msgstr "" #: main/src/ui/unified_window.vala:238 msgid "Manage accounts" msgstr "" #: main/src/ui/unified_window.vala:247 msgid "No active conversations" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:275 #, c-format msgid "Downloading %sโ€ฆ" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:281 #, c-format msgid "%s offered: %s" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:283 #, c-format msgid "File offered: %s" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:285 msgid "File offered" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:290 msgid "File transfer failed" msgstr "" #: main/src/ui/conversation_summary/date_separator_populator.vala:85 msgid "Today" msgstr "" #: main/src/ui/conversation_summary/date_separator_populator.vala:98 msgid "%a, %b %d" msgstr "" #: main/src/ui/conversation_summary/chat_state_populator.vala:145 #, c-format msgid "%s, %s and %i others" msgstr "" #: main/src/ui/conversation_summary/chat_state_populator.vala:147 #, c-format msgid "%s, %s and %s" msgstr "" #: main/src/ui/conversation_summary/chat_state_populator.vala:149 #, c-format msgid "%s and %s" msgstr "" #: main/src/ui/conversation_summary/chat_state_populator.vala:154 msgid "is typingโ€ฆ" msgid_plural "are typingโ€ฆ" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_summary/chat_state_populator.vala:156 msgid "has stopped typing" msgstr "" #: main/src/ui/conversation_summary/content_item_widget_factory.vala:59 msgid "Message too long" msgstr "" #: main/src/ui/conversation_summary/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:193 #, no-c-format msgid "%x, %Hโˆถ%M" msgstr "" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:194 #, no-c-format msgid "%x, %lโˆถ%M %p" msgstr "" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:197 #, no-c-format msgid "%b %d, %Hโˆถ%M" msgstr "" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:198 #, no-c-format msgid "%b %d, %lโˆถ%M %p" msgstr "" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:201 #, no-c-format msgid "%a, %Hโˆถ%M" msgstr "" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:202 #, no-c-format msgid "%a, %lโˆถ%M %p" msgstr "" #: main/src/ui/occupant_menu/list.vala:102 msgid "Owner" msgstr "" #: main/src/ui/occupant_menu/list.vala:104 msgid "Admin" msgstr "" #: main/src/ui/occupant_menu/list.vala:106 msgid "Member" msgstr "" #: main/src/ui/occupant_menu/list.vala:108 msgid "User" msgstr "" #: main/src/ui/occupant_menu/view.vala:29 #: main/src/ui/occupant_menu/view.vala:38 msgid "Invite" msgstr "" #: main/src/ui/occupant_menu/view.vala:37 msgid "Invite to Conference" msgstr "" #: main/src/ui/occupant_menu/view.vala:88 msgid "Start private conversation" msgstr "" #: main/src/ui/occupant_menu/view.vala:96 msgid "Kick" msgstr "" #: main/src/ui/global_search.vala:140 #, c-format msgid "%i search results" msgstr "" #: main/src/ui/global_search.vala:167 #, c-format msgid "In %s" msgstr "" #: main/src/ui/global_search.vala:167 #, c-format msgid "With %s" msgstr "" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:24 msgid "Contact Details" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Discover real JIDs" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who may discover real JIDs?" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 msgid "Password" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "Password required for room entry, if any" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "" #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/src/ui/contact_details/settings_provider.vala:12 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:82 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:124 msgid "On" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:84 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "Off" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:86 msgid "Only when mentioned" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:88 #: main/src/ui/contact_details/settings_provider.vala:123 #, c-format msgid "Default: %s" msgstr "" #: main/src/ui/conversation_titlebar/search_entry.vala:11 msgid "Search messages" msgstr "" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "" #: main/data/manage_accounts/dialog.ui:9 main/data/menu_app.ui:7 msgid "Accounts" msgstr "" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "" #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "" #: main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "" #: main/data/menu_app.ui:21 msgid "About Dino" msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:16 msgid "Main window with conversations" msgstr "" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "" #: main/data/shortcuts.ui:12 msgid "General" msgstr "" #: main/data/shortcuts.ui:32 msgid "Navigation" msgstr "" #: main/data/shortcuts.ui:37 msgid "Jump to next conversation" msgstr "" #: main/data/shortcuts.ui:44 msgid "Jump to previous conversation" msgstr "" #: main/data/global_search.ui:37 msgid "No active search" msgstr "" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "" #: main/data/unified_main_content.ui:147 msgid "You have no open chats" msgstr "" dino-0.1.0/main/po/en.po0000644000000000000000000004675513614354364013475 0ustar rootrootmsgid "" msgstr "" "Project-Id-Version: dino-0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "Language: en\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "Image sent" msgstr "" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "File sent" msgstr "" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "Image received" msgstr "" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "File received" msgstr "" #: main/src/ui/notifications.vala:98 msgid "Subscription request" msgstr "" #: main/src/ui/notifications.vala:105 main/src/ui/notifications.vala:140 #: main/src/ui/conversation_summary/subscription_notification.vala:37 msgid "Accept" msgstr "" #: main/src/ui/notifications.vala:106 main/src/ui/notifications.vala:139 #: main/src/ui/conversation_summary/subscription_notification.vala:38 msgid "Deny" msgstr "" #: main/src/ui/notifications.vala:112 #: main/src/ui/manage_accounts/add_account_dialog.vala:258 #: main/src/ui/add_conversation/conference_details_fragment.vala:188 #, c-format msgid "Could not connect to %s" msgstr "" #: main/src/ui/notifications.vala:128 #, c-format msgid "Invitation to %s" msgstr "" #: main/src/ui/notifications.vala:129 #, c-format msgid "%s invited you to %s" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:152 main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connectingโ€ฆ" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:166 #, c-format msgid "Sign in to %s" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:210 #, c-format msgid "You can now start using %s" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:263 #: main/src/ui/manage_accounts/add_account_dialog.vala:296 #: main/src/ui/manage_accounts/add_account_dialog.vala:306 #: main/src/ui/manage_accounts/add_account_dialog.vala:382 #: main/src/ui/add_conversation/conference_details_fragment.vala:191 msgid "Invalid address" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:278 msgid "Wrong username or password" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Something went wrong" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:321 msgid "No response from server" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 #, c-format msgid "Register on %s" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:330 msgid "The server requires to sign up through a website" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:332 msgid "Open Registration" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:344 msgid "Register" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:346 #, c-format msgid "Check %s for information on how to sign up" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:146 #: main/src/ui/conversation_selector/conversation_selector_row.vala:160 #: main/src/ui/util/helper.vala:136 main/src/ui/util/helper.vala:151 msgid "Me" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:258 #: main/src/ui/conversation_summary/date_separator_populator.vala:100 #, no-c-format msgid "%b %d" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:262 #: main/src/ui/conversation_summary/date_separator_populator.vala:91 msgid "Yesterday" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:265 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:205 #, no-c-format msgid "%Hโˆถ%M" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:266 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:206 #, no-c-format msgid "%lโˆถ%M %p" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:269 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:210 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:271 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:212 msgid "Just now" msgstr "" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/unified_window.vala:249 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 msgid "Join" msgstr "" #: main/src/ui/util/helper.vala:126 #, c-format msgid "%s from %s" msgstr "" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:28 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:42 msgid "Save" msgstr "" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/src/ui/unified_window.vala:248 #: main/data/conversation_list_titlebar_csd.ui:12 #: main/data/conversation_list_titlebar.ui:16 main/data/menu_add.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:20 main/data/shortcuts.ui:17 msgid "Start Conversation" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joiningโ€ฆ" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:169 msgid "Password required to enter room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:174 msgid "Banned from joining or creating conference" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:176 msgid "Room does not exist" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:178 msgid "Not allowed to create room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:180 msgid "Members-only room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:183 msgid "Choose a different nick" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:185 msgid "Too many occupants in room" msgstr "" #: main/src/ui/unified_window.vala:227 msgid "Welcome to Dino!" msgstr "" #: main/src/ui/unified_window.vala:228 msgid "Sign in or create an account to get started." msgstr "" #: main/src/ui/unified_window.vala:229 msgid "Set up account" msgstr "" #: main/src/ui/unified_window.vala:237 msgid "No active accounts" msgstr "" #: main/src/ui/unified_window.vala:238 msgid "Manage accounts" msgstr "" #: main/src/ui/unified_window.vala:247 msgid "No active conversations" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:275 #, c-format msgid "Downloading %sโ€ฆ" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:281 #, c-format msgid "%s offered: %s" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:283 #, c-format msgid "File offered: %s" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:285 msgid "File offered" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:290 msgid "File transfer failed" msgstr "" #: main/src/ui/conversation_summary/date_separator_populator.vala:85 msgid "Today" msgstr "" #: main/src/ui/conversation_summary/date_separator_populator.vala:98 msgid "%a, %b %d" msgstr "" #: main/src/ui/conversation_summary/chat_state_populator.vala:145 #, c-format msgid "%s, %s and %i others" msgstr "" #: main/src/ui/conversation_summary/chat_state_populator.vala:147 #, c-format msgid "%s, %s and %s" msgstr "" #: main/src/ui/conversation_summary/chat_state_populator.vala:149 #, c-format msgid "%s and %s" msgstr "" #: main/src/ui/conversation_summary/chat_state_populator.vala:154 msgid "is typingโ€ฆ" msgid_plural "are typingโ€ฆ" msgstr[0] "" msgstr[1] "" #: main/src/ui/conversation_summary/chat_state_populator.vala:156 msgid "has stopped typing" msgstr "" #: main/src/ui/conversation_summary/content_item_widget_factory.vala:59 msgid "Message too long" msgstr "" #: main/src/ui/conversation_summary/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:193 #, no-c-format msgid "%x, %Hโˆถ%M" msgstr "" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:194 #, no-c-format msgid "%x, %lโˆถ%M %p" msgstr "" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:197 #, no-c-format msgid "%b %d, %Hโˆถ%M" msgstr "" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:198 #, no-c-format msgid "%b %d, %lโˆถ%M %p" msgstr "" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:201 #, no-c-format msgid "%a, %Hโˆถ%M" msgstr "" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:202 #, no-c-format msgid "%a, %lโˆถ%M %p" msgstr "" #: main/src/ui/occupant_menu/list.vala:102 msgid "Owner" msgstr "" #: main/src/ui/occupant_menu/list.vala:104 msgid "Admin" msgstr "" #: main/src/ui/occupant_menu/list.vala:106 msgid "Member" msgstr "" #: main/src/ui/occupant_menu/list.vala:108 msgid "User" msgstr "" #: main/src/ui/occupant_menu/view.vala:29 #: main/src/ui/occupant_menu/view.vala:38 msgid "Invite" msgstr "" #: main/src/ui/occupant_menu/view.vala:37 msgid "Invite to Conference" msgstr "" #: main/src/ui/occupant_menu/view.vala:88 msgid "Start private conversation" msgstr "" #: main/src/ui/occupant_menu/view.vala:96 msgid "Kick" msgstr "" #: main/src/ui/global_search.vala:140 #, c-format msgid "%i search results" msgstr "" #: main/src/ui/global_search.vala:167 #, c-format msgid "In %s" msgstr "" #: main/src/ui/global_search.vala:167 #, c-format msgid "With %s" msgstr "" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:24 msgid "Contact Details" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Discover real JIDs" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who may discover real JIDs?" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 msgid "Password" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "Password required for room entry, if any" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "" #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/src/ui/contact_details/settings_provider.vala:12 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:82 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:124 msgid "On" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:84 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "Off" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:86 msgid "Only when mentioned" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:88 #: main/src/ui/contact_details/settings_provider.vala:123 #, c-format msgid "Default: %s" msgstr "" #: main/src/ui/conversation_titlebar/search_entry.vala:11 msgid "Search messages" msgstr "" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "" #: main/data/manage_accounts/dialog.ui:9 main/data/menu_app.ui:7 msgid "Accounts" msgstr "" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "" #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "" #: main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "" #: main/data/menu_app.ui:21 msgid "About Dino" msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:16 msgid "Main window with conversations" msgstr "" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "" #: main/data/shortcuts.ui:12 msgid "General" msgstr "" #: main/data/shortcuts.ui:32 msgid "Navigation" msgstr "" #: main/data/shortcuts.ui:37 msgid "Jump to next conversation" msgstr "" #: main/data/shortcuts.ui:44 msgid "Jump to previous conversation" msgstr "" #: main/data/global_search.ui:37 msgid "No active search" msgstr "" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "" #: main/data/unified_main_content.ui:147 msgid "You have no open chats" msgstr "" dino-0.1.0/main/po/eo.po0000644000000000000000000005330213614354364013460 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2020-01-12 13:21+0000\n" "Language-Team: Esperanto \n" "Language: eo\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.10.1\n" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "Image sent" msgstr "Bildon sendis" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "File sent" msgstr "Dosieron sendis" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "Image received" msgstr "Bildon ricevis" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "File received" msgstr "Dosieron ricevis" #: main/src/ui/notifications.vala:98 msgid "Subscription request" msgstr "Abona peto" #: main/src/ui/notifications.vala:105 main/src/ui/notifications.vala:140 #: main/src/ui/conversation_summary/subscription_notification.vala:37 msgid "Accept" msgstr "Akcepti" #: main/src/ui/notifications.vala:106 main/src/ui/notifications.vala:139 #: main/src/ui/conversation_summary/subscription_notification.vala:38 msgid "Deny" msgstr "Rifuzi" #: main/src/ui/notifications.vala:112 #: main/src/ui/manage_accounts/add_account_dialog.vala:258 #: main/src/ui/add_conversation/conference_details_fragment.vala:188 #, c-format msgid "Could not connect to %s" msgstr "Ne povus konekti al %s" #: main/src/ui/notifications.vala:128 #, c-format msgid "Invitation to %s" msgstr "invitilo por %s" #: main/src/ui/notifications.vala:129 #, c-format msgid "%s invited you to %s" msgstr "invitilo de %s por %s" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Forigas konton %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Forigas" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Elekti profilbildon" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Elekti" #: main/src/ui/manage_accounts/dialog.vala:152 main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Nuligi" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Bildoj" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "ฤˆiuj dosieroj" #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connectingโ€ฆ" msgstr "Konektadoโ€ฆ" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Konektita" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Malkonektita" #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Malฤusta pasvorto" #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Nevalida TLSa atesto" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Eraro" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Aldoni Konton" #: main/src/ui/manage_accounts/add_account_dialog.vala:166 #, c-format msgid "Sign in to %s" msgstr "Signo en al %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:210 #, c-format msgid "You can now start using %s" msgstr "Vi nun povas komenci uzanta %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:263 #: main/src/ui/manage_accounts/add_account_dialog.vala:296 #: main/src/ui/manage_accounts/add_account_dialog.vala:306 #: main/src/ui/manage_accounts/add_account_dialog.vala:382 #: main/src/ui/add_conversation/conference_details_fragment.vala:191 msgid "Invalid address" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:278 msgid "Wrong username or password" msgstr "Malฤusta uzantnomo aลญ pasvorto" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Something went wrong" msgstr "Io misis" #: main/src/ui/manage_accounts/add_account_dialog.vala:321 msgid "No response from server" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 #, c-format msgid "Register on %s" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:330 msgid "The server requires to sign up through a website" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:332 msgid "Open Registration" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:344 msgid "Register" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:346 #, c-format msgid "Check %s for information on how to sign up" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:146 #: main/src/ui/conversation_selector/conversation_selector_row.vala:160 #: main/src/ui/util/helper.vala:136 main/src/ui/util/helper.vala:151 msgid "Me" msgstr "Mi" #: main/src/ui/conversation_selector/conversation_selector_row.vala:258 #: main/src/ui/conversation_summary/date_separator_populator.vala:100 #, no-c-format msgid "%b %d" msgstr "%b %d" #: main/src/ui/conversation_selector/conversation_selector_row.vala:262 #: main/src/ui/conversation_summary/date_separator_populator.vala:91 msgid "Yesterday" msgstr "Hieraลญ" #: main/src/ui/conversation_selector/conversation_selector_row.vala:265 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:205 #, no-c-format msgid "%Hโˆถ%M" msgstr "%Hโˆถ%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:266 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:206 #, no-c-format msgid "%lโˆถ%M %p" msgstr "%lโˆถ%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:269 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:210 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "Antaลญ %i minuto" msgstr[1] "Antaลญ %i minutoj" #: main/src/ui/conversation_selector/conversation_selector_row.vala:271 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:212 msgid "Just now" msgstr "ฤดus" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/unified_window.vala:249 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 msgid "Join" msgstr "Aliฤi" #: main/src/ui/util/helper.vala:126 #, c-format msgid "%s from %s" msgstr "" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:28 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Aldoni" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:42 msgid "Save" msgstr "Konservi" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Sekva" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Retre" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/src/ui/unified_window.vala:248 #: main/data/conversation_list_titlebar_csd.ui:12 #: main/data/conversation_list_titlebar.ui:16 main/data/menu_add.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:20 main/data/shortcuts.ui:17 msgid "Start Conversation" msgstr "Komenci Konversacio" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Komenci" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joiningโ€ฆ" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:169 msgid "Password required to enter room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:174 msgid "Banned from joining or creating conference" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:176 msgid "Room does not exist" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:178 msgid "Not allowed to create room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:180 msgid "Members-only room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:183 msgid "Choose a different nick" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:185 msgid "Too many occupants in room" msgstr "" #: main/src/ui/unified_window.vala:227 msgid "Welcome to Dino!" msgstr "" #: main/src/ui/unified_window.vala:228 msgid "Sign in or create an account to get started." msgstr "" #: main/src/ui/unified_window.vala:229 msgid "Set up account" msgstr "" #: main/src/ui/unified_window.vala:237 msgid "No active accounts" msgstr "Neniuj aktivaj kontoj" #: main/src/ui/unified_window.vala:238 msgid "Manage accounts" msgstr "Administri kontojn" #: main/src/ui/unified_window.vala:247 msgid "No active conversations" msgstr "Neniu aktiva interparolo" #: main/src/ui/conversation_summary/file_widget.vala:275 #, c-format msgid "Downloading %sโ€ฆ" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:281 #, c-format msgid "%s offered: %s" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:283 #, c-format msgid "File offered: %s" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:285 msgid "File offered" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:290 msgid "File transfer failed" msgstr "" #: main/src/ui/conversation_summary/date_separator_populator.vala:85 msgid "Today" msgstr "" #: main/src/ui/conversation_summary/date_separator_populator.vala:98 msgid "%a, %b %d" msgstr "" #: main/src/ui/conversation_summary/chat_state_populator.vala:145 #, c-format msgid "%s, %s and %i others" msgstr "" #: main/src/ui/conversation_summary/chat_state_populator.vala:147 #, c-format msgid "%s, %s and %s" msgstr "" #: main/src/ui/conversation_summary/chat_state_populator.vala:149 #, c-format msgid "%s and %s" msgstr "" #: main/src/ui/conversation_summary/chat_state_populator.vala:154 #, fuzzy msgid "is typingโ€ฆ" msgid_plural "are typingโ€ฆ" msgstr[0] "tajpasโ€ฆ" msgstr[1] "tajpasโ€ฆ" #: main/src/ui/conversation_summary/chat_state_populator.vala:156 msgid "has stopped typing" msgstr "ฤ‰esis tajpi" #: main/src/ui/conversation_summary/content_item_widget_factory.vala:59 msgid "Message too long" msgstr "" #: main/src/ui/conversation_summary/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:193 #, no-c-format msgid "%x, %Hโˆถ%M" msgstr "%x, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:194 #, no-c-format msgid "%x, %lโˆถ%M %p" msgstr "%x, %lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:197 #, no-c-format msgid "%b %d, %Hโˆถ%M" msgstr "%b %d, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:198 #, no-c-format msgid "%b %d, %lโˆถ%M %p" msgstr "%b %d, %lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:201 #, no-c-format msgid "%a, %Hโˆถ%M" msgstr "%a, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:202 #, no-c-format msgid "%a, %lโˆถ%M %p" msgstr "%a, %lโˆถ%M %p" #: main/src/ui/occupant_menu/list.vala:102 msgid "Owner" msgstr "Posedanto" #: main/src/ui/occupant_menu/list.vala:104 msgid "Admin" msgstr "Administranto" #: main/src/ui/occupant_menu/list.vala:106 msgid "Member" msgstr "Ano" #: main/src/ui/occupant_menu/list.vala:108 msgid "User" msgstr "Uzanto" #: main/src/ui/occupant_menu/view.vala:29 #: main/src/ui/occupant_menu/view.vala:38 msgid "Invite" msgstr "" #: main/src/ui/occupant_menu/view.vala:37 msgid "Invite to Conference" msgstr "" #: main/src/ui/occupant_menu/view.vala:88 msgid "Start private conversation" msgstr "Komenci privatan interparolon" #: main/src/ui/occupant_menu/view.vala:96 msgid "Kick" msgstr "" #: main/src/ui/global_search.vala:140 #, c-format msgid "%i search results" msgstr "" #: main/src/ui/global_search.vala:167 #, c-format msgid "In %s" msgstr "" #: main/src/ui/global_search.vala:167 #, c-format msgid "With %s" msgstr "" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:24 msgid "Contact Details" msgstr "Kontaktaj Detaloj" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Nomo de la ฤ‰ambro" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Priskribo de la ฤ‰ambro" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Daลญra" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "La ฤ‰ambro daลญros post eliro de la lasta ฤ‰eestinto" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Publike serฤ‰ebla" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "ฤˆeestantoj rajtas ลanฤi temon" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Discover real JIDs" msgstr "Eltrovi verajn JID-ojn" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who may discover real JIDs?" msgstr "Kiu rajtas eltrovi verajn JID-ojn?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 msgid "Password" msgstr "Pasvorto" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "Password required for room entry, if any" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Kontrolata" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Nur anoj" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Nur anoj rajtas eniri la ฤ‰ambron" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Mesaฤa historio" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Maksimuma nombro da historiaj mesaฤoj donotaj de la ฤ‰ambro" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "ฤˆambraj Agordoj" #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/src/ui/contact_details/settings_provider.vala:12 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Agordoj" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Lokaj Agordoj" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Sciigi pri tajpado" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Sciigoj" #: main/src/ui/contact_details/settings_provider.vala:82 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:124 msgid "On" msgstr "Ek" #: main/src/ui/contact_details/settings_provider.vala:84 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "Off" msgstr "For" #: main/src/ui/contact_details/settings_provider.vala:86 msgid "Only when mentioned" msgstr "Nur menciite" #: main/src/ui/contact_details/settings_provider.vala:88 #: main/src/ui/contact_details/settings_provider.vala:123 #, c-format msgid "Default: %s" msgstr "" #: main/src/ui/conversation_titlebar/search_entry.vala:11 msgid "Search messages" msgstr "" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Sciigi pri envenaj mesaฤoj" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Grafikigi tekstajn mienetojn" #: main/data/manage_accounts/dialog.ui:9 main/data/menu_app.ui:7 msgid "Accounts" msgstr "Kontoj" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Loka kaลnomo" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Aldoni konton" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "" #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Neฤ‰ifrita" #: main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "" #: main/data/menu_app.ui:21 msgid "About Dino" msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:16 msgid "Main window with conversations" msgstr "" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Konto" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Kaลnomo" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Kaลnomo" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "" #: main/data/shortcuts.ui:12 msgid "General" msgstr "" #: main/data/shortcuts.ui:32 msgid "Navigation" msgstr "" #: main/data/shortcuts.ui:37 msgid "Jump to next conversation" msgstr "" #: main/data/shortcuts.ui:44 msgid "Jump to previous conversation" msgstr "" #: main/data/global_search.ui:37 msgid "No active search" msgstr "" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "" #: main/data/unified_main_content.ui:147 msgid "You have no open chats" msgstr "" #~ msgid "Failed connecting to %s" #~ msgstr "Malsukcesis konektanta al %s" #~ msgid "Join Conference" #~ msgstr "Aliฤi al Kunveno" #~ msgid "Preferences" #~ msgstr "Preferoj" #~ msgid "Quit" #~ msgstr "ฤˆesi" #~ msgid "Search" #~ msgstr "Serฤ‰i" #~ msgid "Send message marker" #~ msgstr "Sendi mesaฤmarkilon" #~ msgid "Start Chat" #~ msgstr "Ekbabili" #~ msgid "Request presence updates" #~ msgstr "Peti sciigojn pri ฤ‰eesto" #~ msgid "Join on startup" #~ msgstr "Aliฤi je lanฤ‰o" #~ msgid "Add Chat" #~ msgstr "Aldoni Babilon" dino-0.1.0/main/po/es.po0000644000000000000000000006212313614354364013465 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2019-12-28 16:21+0000\n" "Language-Team: Spanish \n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.10\n" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "Image sent" msgstr "Imagen enviada" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "File sent" msgstr "Archivo enviado" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "Image received" msgstr "Imagen recibida" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "File received" msgstr "Archivo recibido" #: main/src/ui/notifications.vala:98 msgid "Subscription request" msgstr "Solicitud de suscripciรณn" #: main/src/ui/notifications.vala:105 main/src/ui/notifications.vala:140 #: main/src/ui/conversation_summary/subscription_notification.vala:37 msgid "Accept" msgstr "Aceptar" #: main/src/ui/notifications.vala:106 main/src/ui/notifications.vala:139 #: main/src/ui/conversation_summary/subscription_notification.vala:38 msgid "Deny" msgstr "Denegar" #: main/src/ui/notifications.vala:112 #: main/src/ui/manage_accounts/add_account_dialog.vala:258 #: main/src/ui/add_conversation/conference_details_fragment.vala:188 #, c-format msgid "Could not connect to %s" msgstr "No se pudo conectar a %s" #: main/src/ui/notifications.vala:128 #, c-format msgid "Invitation to %s" msgstr "Invitaciรณn para %s" #: main/src/ui/notifications.vala:129 #, c-format msgid "%s invited you to %s" msgstr "%s te invitรณ a %s" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "ยฟEliminar cuenta %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Eliminar" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Seleccionar imagen de perfil" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Seleccionar" #: main/src/ui/manage_accounts/dialog.vala:152 main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Cancelar" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Imรกgenes" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Todos los archivos" #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connectingโ€ฆ" msgstr "Conectandoโ€ฆ" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Conectado" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Desconectado" #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Contraseรฑa incorrecta" #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Certificado TLS invรกlido" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Error" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Aรฑadir Cuenta" #: main/src/ui/manage_accounts/add_account_dialog.vala:166 #, c-format msgid "Sign in to %s" msgstr "Iniciar sesiรณn con %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:210 #, c-format msgid "You can now start using %s" msgstr "Puedes empezar a usar %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:263 #: main/src/ui/manage_accounts/add_account_dialog.vala:296 #: main/src/ui/manage_accounts/add_account_dialog.vala:306 #: main/src/ui/manage_accounts/add_account_dialog.vala:382 #: main/src/ui/add_conversation/conference_details_fragment.vala:191 msgid "Invalid address" msgstr "Direcciรณn invรกlida" #: main/src/ui/manage_accounts/add_account_dialog.vala:278 msgid "Wrong username or password" msgstr "Usuario o contraseรฑa incorrecta" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Something went wrong" msgstr "Algo saliรณ mal" #: main/src/ui/manage_accounts/add_account_dialog.vala:321 msgid "No response from server" msgstr "No hay respuesta del servidor" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 #, c-format msgid "Register on %s" msgstr "Registrarte en %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:330 msgid "The server requires to sign up through a website" msgstr "El servidor requiere registro a travรฉs de un sitio web" #: main/src/ui/manage_accounts/add_account_dialog.vala:332 msgid "Open Registration" msgstr "Registro abierto" #: main/src/ui/manage_accounts/add_account_dialog.vala:344 msgid "Register" msgstr "Registro" #: main/src/ui/manage_accounts/add_account_dialog.vala:346 #, c-format msgid "Check %s for information on how to sign up" msgstr "Mira %s para mas informaciรณn sobre el registro" #: main/src/ui/conversation_selector/conversation_selector_row.vala:146 #: main/src/ui/conversation_selector/conversation_selector_row.vala:160 #: main/src/ui/util/helper.vala:136 main/src/ui/util/helper.vala:151 msgid "Me" msgstr "Yo" #: main/src/ui/conversation_selector/conversation_selector_row.vala:258 #: main/src/ui/conversation_summary/date_separator_populator.vala:100 #, no-c-format msgid "%b %d" msgstr "%d %b" #: main/src/ui/conversation_selector/conversation_selector_row.vala:262 #: main/src/ui/conversation_summary/date_separator_populator.vala:91 msgid "Yesterday" msgstr "Ayer" #: main/src/ui/conversation_selector/conversation_selector_row.vala:265 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:205 #, no-c-format msgid "%Hโˆถ%M" msgstr "%Hโˆถ%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:266 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:206 #, no-c-format msgid "%lโˆถ%M %p" msgstr "%lโˆถ%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:269 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:210 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "hace %i min" msgstr[1] "hace %i mins" #: main/src/ui/conversation_selector/conversation_selector_row.vala:271 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:212 msgid "Just now" msgstr "Ahora" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/unified_window.vala:249 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Unirse a canal" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 msgid "Join" msgstr "Unirse" #: main/src/ui/util/helper.vala:126 #, c-format msgid "%s from %s" msgstr "%s de %s" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:28 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Aรฑadir" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:42 msgid "Save" msgstr "Guardar" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Siguiente" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Volver" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/src/ui/unified_window.vala:248 #: main/data/conversation_list_titlebar_csd.ui:12 #: main/data/conversation_list_titlebar.ui:16 main/data/menu_add.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:20 main/data/shortcuts.ui:17 msgid "Start Conversation" msgstr "Iniciar Conversaciรณn" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Iniciar" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joiningโ€ฆ" msgstr "Uniรฉndoseโ€ฆ" #: main/src/ui/add_conversation/conference_details_fragment.vala:169 msgid "Password required to enter room" msgstr "Contraseรฑa requerida para entrar en la conversaciรณn en grupo" #: main/src/ui/add_conversation/conference_details_fragment.vala:174 msgid "Banned from joining or creating conference" msgstr "" "Tu entrada ha sido prohibida o no puedes crear la conversaciรณn en grupo" #: main/src/ui/add_conversation/conference_details_fragment.vala:176 msgid "Room does not exist" msgstr "La conversaciรณn en grupo no existe" #: main/src/ui/add_conversation/conference_details_fragment.vala:178 msgid "Not allowed to create room" msgstr "No estรก permitido crear la conversaciรณn en grupo" #: main/src/ui/add_conversation/conference_details_fragment.vala:180 msgid "Members-only room" msgstr "La conversaciรณn es solo para miembros" #: main/src/ui/add_conversation/conference_details_fragment.vala:183 msgid "Choose a different nick" msgstr "Elige un alias diferente" #: main/src/ui/add_conversation/conference_details_fragment.vala:185 msgid "Too many occupants in room" msgstr "La conversaciรณn tiene demasiados ocupantes" #: main/src/ui/unified_window.vala:227 msgid "Welcome to Dino!" msgstr "ยกBienvenido a Dino!" #: main/src/ui/unified_window.vala:228 msgid "Sign in or create an account to get started." msgstr "Inicia sesiรณn o crea una nueva cuenta para empezar." #: main/src/ui/unified_window.vala:229 msgid "Set up account" msgstr "Configurar cuenta" #: main/src/ui/unified_window.vala:237 msgid "No active accounts" msgstr "No hay cuentas activas" #: main/src/ui/unified_window.vala:238 msgid "Manage accounts" msgstr "Gestionar cuentas" #: main/src/ui/unified_window.vala:247 msgid "No active conversations" msgstr "No hay conversaciones activa" #: main/src/ui/conversation_summary/file_widget.vala:275 #, c-format msgid "Downloading %sโ€ฆ" msgstr "Descargando %sโ€ฆ" #: main/src/ui/conversation_summary/file_widget.vala:281 #, c-format msgid "%s offered: %s" msgstr "%s ofreciรณ: %s" #: main/src/ui/conversation_summary/file_widget.vala:283 #, c-format msgid "File offered: %s" msgstr "Archivo ofrecido: %s" #: main/src/ui/conversation_summary/file_widget.vala:285 msgid "File offered" msgstr "Archivo ofrecido" #: main/src/ui/conversation_summary/file_widget.vala:290 msgid "File transfer failed" msgstr "Transferencia de archivo fallida" #: main/src/ui/conversation_summary/date_separator_populator.vala:85 msgid "Today" msgstr "Hoy" #: main/src/ui/conversation_summary/date_separator_populator.vala:98 msgid "%a, %b %d" msgstr "%a, %d %b" #: main/src/ui/conversation_summary/chat_state_populator.vala:145 #, c-format msgid "%s, %s and %i others" msgstr "%s, %s y %i otros" #: main/src/ui/conversation_summary/chat_state_populator.vala:147 #, c-format msgid "%s, %s and %s" msgstr "%s, %s y %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:149 #, c-format msgid "%s and %s" msgstr "%s y %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:154 msgid "is typingโ€ฆ" msgid_plural "are typingโ€ฆ" msgstr[0] "estรก escribiendoโ€ฆ" msgstr[1] "estรกn escribiendoโ€ฆ" #: main/src/ui/conversation_summary/chat_state_populator.vala:156 msgid "has stopped typing" msgstr "ha dejado de escribir" #: main/src/ui/conversation_summary/content_item_widget_factory.vala:59 msgid "Message too long" msgstr "Mensaje demasiado largo" #: main/src/ui/conversation_summary/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "A este contacto le gustarรญa aรฑadirte a su lista de contactos" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:193 #, no-c-format msgid "%x, %Hโˆถ%M" msgstr "%x, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:194 #, no-c-format msgid "%x, %lโˆถ%M %p" msgstr "%x, %lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:197 #, no-c-format msgid "%b %d, %Hโˆถ%M" msgstr "%d %b, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:198 #, no-c-format msgid "%b %d, %lโˆถ%M %p" msgstr "%d %b, %lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:201 #, no-c-format msgid "%a, %Hโˆถ%M" msgstr "%a, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:202 #, no-c-format msgid "%a, %lโˆถ%M %p" msgstr "%a, %lโˆถ%M %p" #: main/src/ui/occupant_menu/list.vala:102 msgid "Owner" msgstr "Propietario" #: main/src/ui/occupant_menu/list.vala:104 msgid "Admin" msgstr "Administrador" #: main/src/ui/occupant_menu/list.vala:106 msgid "Member" msgstr "Miembro" #: main/src/ui/occupant_menu/list.vala:108 msgid "User" msgstr "Usuario" #: main/src/ui/occupant_menu/view.vala:29 #: main/src/ui/occupant_menu/view.vala:38 msgid "Invite" msgstr "Invitar" #: main/src/ui/occupant_menu/view.vala:37 msgid "Invite to Conference" msgstr "Invitar a Conversaciรณn en grupo" #: main/src/ui/occupant_menu/view.vala:88 msgid "Start private conversation" msgstr "Iniciar conversaciรณn privada" #: main/src/ui/occupant_menu/view.vala:96 msgid "Kick" msgstr "Expulsar" #: main/src/ui/global_search.vala:140 #, c-format msgid "%i search results" msgstr "%i resultados de la bรบsqueda" #: main/src/ui/global_search.vala:167 #, c-format msgid "In %s" msgstr "En %s" #: main/src/ui/global_search.vala:167 #, c-format msgid "With %s" msgstr "Con %s" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Detalles de la Conversaciรณn" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:24 msgid "Contact Details" msgstr "Detalles del contacto" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Nombre de la conversaciรณn en grupo" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Descripciรณn de la conversaciรณn en grupo" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Conversaciรณn permanente" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "La conversaciรณn en grupo no se eliminarรก aunque no haya participantes" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Conversaciรณn pรบblica" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Los ocupantes pueden cambiar el asunto de la conversaciรณn" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Discover real JIDs" msgstr "Descubrir JIDs reales" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who may discover real JIDs?" msgstr "ยฟQuiรฉn puede ver los JIDs reales?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 msgid "Password" msgstr "Contraseรฑa" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "Password required for room entry, if any" msgstr "" "Contraseรฑa requerida para entrar en la conversaciรณn en grupo. Dejar vacรญo " "para ninguna" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Conversaciรณn moderada" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Solo los participantes con voz pueden enviar mensajes" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Conversaciรณn privada solo para miembros" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Solo los miembros pueden entrar en la conversaciรณn en grupo" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Histรณrico de mensajes" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Nรบmero mรกximo de mensajes que se almacenan en servidor" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Configuraciรณn de la conversaciรณn en grupo" #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/src/ui/contact_details/settings_provider.vala:12 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Ajustes" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Bloquear" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "Las comunicaciones y las actualizaciones de presencia serรกn bloqueadas" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Ajustes locales" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Enviar notificaciones cuando escribes" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Enviar confirmaciones de lectura" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Notificaciones" #: main/src/ui/contact_details/settings_provider.vala:82 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:124 msgid "On" msgstr "Sรญ" #: main/src/ui/contact_details/settings_provider.vala:84 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "Off" msgstr "No" #: main/src/ui/contact_details/settings_provider.vala:86 msgid "Only when mentioned" msgstr "Solo cuando te mencionan en la conversaciรณn" #: main/src/ui/contact_details/settings_provider.vala:88 #: main/src/ui/contact_details/settings_provider.vala:123 #, c-format msgid "Default: %s" msgstr "Por defecto: %s" #: main/src/ui/conversation_titlebar/search_entry.vala:11 msgid "Search messages" msgstr "Buscar mensajes" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Miembros" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Notificar cuando llega un nuevo mensaje" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Convertir smileys en emojis" #: main/data/manage_accounts/dialog.ui:9 main/data/menu_app.ui:7 msgid "Accounts" msgstr "Cuentas" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Alias local" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "No hay cuentas activas" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Aรฑadir una cuenta" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Iniciar Sesiรณn" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Crear cuenta" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "No se pudo establecer una conexiรณn segura" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Conectar" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Escoger un servidor publico" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "O especifica la direcciรณn del servidor" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Volver a Iniciar sesiรณn" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Escoge otro servidor" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "ยกTodo preparado!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Terminar" #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Sin cifrar" #: main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Atajos de teclado" #: main/data/menu_app.ui:21 msgid "About Dino" msgstr "Sobre Dino" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Cliente de XMPP moderno" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino es un cliente de mensajerรญa moderno y libre para escritorio. Estรก " "enfocado en proveer una experiencia Jabber/XMPP limpia y confiable teniendo " "tu privacidad en mente." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Soporta cifrado de extremo a extremo a travรฉs de OMEMO y OpenPGP y permite " "configurar las caracterรญsticas relacionadas con la privacidad, como " "confirmaciones de lectura y notificaciones de escritura." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino recupera los mensajes desde el servidor y sincroniza los mensajes con " "otros dispositivos." #: main/data/im.dino.Dino.appdata.xml.in:16 msgid "Main window with conversations" msgstr "Ventana principal con conversaciones" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Cuenta" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Alias" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Alias" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Aรฑadir contacto" #: main/data/shortcuts.ui:12 msgid "General" msgstr "General" #: main/data/shortcuts.ui:32 msgid "Navigation" msgstr "Navegaciรณn" #: main/data/shortcuts.ui:37 msgid "Jump to next conversation" msgstr "Saltar a siguiente conversaciรณn" #: main/data/shortcuts.ui:44 msgid "Jump to previous conversation" msgstr "Saltar a conversaciรณn previa" #: main/data/global_search.ui:37 msgid "No active search" msgstr "Bรบsqueda no activa" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Escribe para empezar una bรบsqueda" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Sin mensajes que coincidan" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Verifique la ortografรญa o intente eliminar los filtros" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "" "Pulsa aquรญ para empezar una nueva conversaciรณn o unirse a una conversaciรณn " "en grupo." #: main/data/unified_main_content.ui:147 msgid "You have no open chats" msgstr "No tienes conversaciones abiertas" #~ msgid "Failed connecting to %s" #~ msgstr "Fallo conectando con %s" #~ msgid "Join Conference" #~ msgstr "Unirse a Conversaciรณn en grupo" #~ msgid "File" #~ msgstr "Archivo" #~ msgid "Communicate happiness." #~ msgstr "Comunรญcate felizmente." #~ msgid "Preferences" #~ msgstr "Preferencias" #~ msgid "Quit" #~ msgstr "Salir" #~ msgid "JID should be of the form โ€œuser@example.comโ€" #~ msgstr "El JID debe tener la forma \"usuario@ejemplo.com\"" #~ msgid "Copy Link Address" #~ msgstr "Copiar direcciรณn del enlace" #~ msgid "Copy" #~ msgstr "Copiar" #~ msgid "Select All" #~ msgstr "Seleccionar todo" #~ msgid "Search" #~ msgstr "Buscar" #~ msgid "Send message marker" #~ msgstr "Enviar confirmaciรณn al recibir y al leer los mensajes" #~ msgid "Start Chat" #~ msgstr "Iniciar Conversaciรณn" #~ msgid "Request presence updates" #~ msgstr "Solicitar actualizaciones de presencia" #~ msgid "This is an app-notification. Click the button to dismiss" #~ msgstr "" #~ "Esto es una notificaciรณn de la aplicaciรณn. Haz click en el botรณn para " #~ "descartar" #~ msgid "Join on startup" #~ msgstr "Unirse al inicio" #~ msgid "Add Chat" #~ msgstr "Aรฑadir conversaciรณn" dino-0.1.0/main/po/eu.po0000644000000000000000000006001713614354364013467 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2019-12-21 12:21+0000\n" "Language-Team: Basque \n" "Language: eu\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.10\n" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "Image sent" msgstr "Irudia bidali da" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "File sent" msgstr "Fitxategia bidali da" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "Image received" msgstr "Irudia jaso da" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "File received" msgstr "Fitxategia jaso da" #: main/src/ui/notifications.vala:98 msgid "Subscription request" msgstr "Harpidetzaren eskaera" #: main/src/ui/notifications.vala:105 main/src/ui/notifications.vala:140 #: main/src/ui/conversation_summary/subscription_notification.vala:37 msgid "Accept" msgstr "Onartu" #: main/src/ui/notifications.vala:106 main/src/ui/notifications.vala:139 #: main/src/ui/conversation_summary/subscription_notification.vala:38 msgid "Deny" msgstr "Ukatu" #: main/src/ui/notifications.vala:112 #: main/src/ui/manage_accounts/add_account_dialog.vala:258 #: main/src/ui/add_conversation/conference_details_fragment.vala:188 #, c-format msgid "Could not connect to %s" msgstr "Ezin izan da %s(e)ra konektatu" #: main/src/ui/notifications.vala:128 #, c-format msgid "Invitation to %s" msgstr "" #: main/src/ui/notifications.vala:129 #, c-format msgid "%s invited you to %s" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "%s kontua kendu?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Kendu" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Irudia hautatu" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Hautatu" #: main/src/ui/manage_accounts/dialog.vala:152 main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Utzi" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Irudiak" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Fitxategi guztiak" #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connectingโ€ฆ" msgstr "Konektatzenโ€ฆ" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Konektatuta" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Ez konektatuta" #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Pasahitz okerra" #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "TLS ziurtagiri ez balidouna" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Akatsa" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Kontua gehitu" #: main/src/ui/manage_accounts/add_account_dialog.vala:166 #, c-format msgid "Sign in to %s" msgstr "Hasi saioa %s(e)n" #: main/src/ui/manage_accounts/add_account_dialog.vala:210 #, c-format msgid "You can now start using %s" msgstr "Orain %s erabiltzen hasi zaitezke" #: main/src/ui/manage_accounts/add_account_dialog.vala:263 #: main/src/ui/manage_accounts/add_account_dialog.vala:296 #: main/src/ui/manage_accounts/add_account_dialog.vala:306 #: main/src/ui/manage_accounts/add_account_dialog.vala:382 #: main/src/ui/add_conversation/conference_details_fragment.vala:191 msgid "Invalid address" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:278 msgid "Wrong username or password" msgstr "Erabiltzaile izen edo pasahitz okerra" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Something went wrong" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:321 msgid "No response from server" msgstr "Erantzunik ez zerbitzaritik" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 #, c-format msgid "Register on %s" msgstr "Izena eman hemen: %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:330 msgid "The server requires to sign up through a website" msgstr "Zerbitzariak webgune baten bidez izena ematea eskatzen du" #: main/src/ui/manage_accounts/add_account_dialog.vala:332 msgid "Open Registration" msgstr "Izen emate irekia" #: main/src/ui/manage_accounts/add_account_dialog.vala:344 msgid "Register" msgstr "Izena eman" #: main/src/ui/manage_accounts/add_account_dialog.vala:346 #, c-format msgid "Check %s for information on how to sign up" msgstr "Ikusi %s izena ematearen inguruko informazioa lortzeko" #: main/src/ui/conversation_selector/conversation_selector_row.vala:146 #: main/src/ui/conversation_selector/conversation_selector_row.vala:160 #: main/src/ui/util/helper.vala:136 main/src/ui/util/helper.vala:151 msgid "Me" msgstr "Ni" #: main/src/ui/conversation_selector/conversation_selector_row.vala:258 #: main/src/ui/conversation_summary/date_separator_populator.vala:100 #, no-c-format msgid "%b %d" msgstr "%b %d" #: main/src/ui/conversation_selector/conversation_selector_row.vala:262 #: main/src/ui/conversation_summary/date_separator_populator.vala:91 msgid "Yesterday" msgstr "Atzo" #: main/src/ui/conversation_selector/conversation_selector_row.vala:265 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:205 #, no-c-format msgid "%Hโˆถ%M" msgstr "%Hโˆถ%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:266 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:206 #, no-c-format msgid "%lโˆถ%M %p" msgstr "%lโˆถ%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:269 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:210 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "orain dela min %i" msgstr[1] "orain dela %i min" #: main/src/ui/conversation_selector/conversation_selector_row.vala:271 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:212 msgid "Just now" msgstr "Orain" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/unified_window.vala:249 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Kanalera gehitu" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 msgid "Join" msgstr "Batu" #: main/src/ui/util/helper.vala:126 #, c-format msgid "%s from %s" msgstr "" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:28 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Gehitu" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:42 msgid "Save" msgstr "Gorde" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Hurrengoa" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Atzera" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/src/ui/unified_window.vala:248 #: main/data/conversation_list_titlebar_csd.ui:12 #: main/data/conversation_list_titlebar.ui:16 main/data/menu_add.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:20 main/data/shortcuts.ui:17 msgid "Start Conversation" msgstr "Elkarrizketa hasi" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Hasi" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joiningโ€ฆ" msgstr "Batzenโ€ฆ" #: main/src/ui/add_conversation/conference_details_fragment.vala:169 msgid "Password required to enter room" msgstr "Pasahitza behar da gelara sartzeko" #: main/src/ui/add_conversation/conference_details_fragment.vala:174 msgid "Banned from joining or creating conference" msgstr "Konferentzia sortu edo batzea debekatuta" #: main/src/ui/add_conversation/conference_details_fragment.vala:176 msgid "Room does not exist" msgstr "Gela ez da existitzen" #: main/src/ui/add_conversation/conference_details_fragment.vala:178 msgid "Not allowed to create room" msgstr "Ez duzu gela sortzeko baimenik" #: main/src/ui/add_conversation/conference_details_fragment.vala:180 msgid "Members-only room" msgstr "Gela kideentzat da soilik" #: main/src/ui/add_conversation/conference_details_fragment.vala:183 msgid "Choose a different nick" msgstr "Ezizen ezberdin bat hautatu" #: main/src/ui/add_conversation/conference_details_fragment.vala:185 msgid "Too many occupants in room" msgstr "Gelak kide gehiegi ditu" #: main/src/ui/unified_window.vala:227 msgid "Welcome to Dino!" msgstr "Ongi etorri Dinora!" #: main/src/ui/unified_window.vala:228 msgid "Sign in or create an account to get started." msgstr "" #: main/src/ui/unified_window.vala:229 msgid "Set up account" msgstr "Kontu bat ezarri" #: main/src/ui/unified_window.vala:237 msgid "No active accounts" msgstr "Ez dago kontu aktiborik" #: main/src/ui/unified_window.vala:238 msgid "Manage accounts" msgstr "Kontuak kudeatu" #: main/src/ui/unified_window.vala:247 msgid "No active conversations" msgstr "Ez dago solasaldi aktiborik" #: main/src/ui/conversation_summary/file_widget.vala:275 #, c-format msgid "Downloading %sโ€ฆ" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:281 #, c-format msgid "%s offered: %s" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:283 #, c-format msgid "File offered: %s" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:285 msgid "File offered" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:290 msgid "File transfer failed" msgstr "" #: main/src/ui/conversation_summary/date_separator_populator.vala:85 msgid "Today" msgstr "Gaur" #: main/src/ui/conversation_summary/date_separator_populator.vala:98 msgid "%a, %b %d" msgstr "%a, %b %d" #: main/src/ui/conversation_summary/chat_state_populator.vala:145 #, c-format msgid "%s, %s and %i others" msgstr "%s, %s eta beste %i" #: main/src/ui/conversation_summary/chat_state_populator.vala:147 #, c-format msgid "%s, %s and %s" msgstr "%s, %s eta %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:149 #, c-format msgid "%s and %s" msgstr "%s eta %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:154 msgid "is typingโ€ฆ" msgid_plural "are typingโ€ฆ" msgstr[0] "idazten ari daโ€ฆ" msgstr[1] "idazten ari diraโ€ฆ" #: main/src/ui/conversation_summary/chat_state_populator.vala:156 msgid "has stopped typing" msgstr "idazteari utzi dio" #: main/src/ui/conversation_summary/content_item_widget_factory.vala:59 msgid "Message too long" msgstr "Mezu luzeegia" #: main/src/ui/conversation_summary/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Kontaktu honek bere kontaktuen zerrendara gehitu nahi zaitu" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:193 #, no-c-format msgid "%x, %Hโˆถ%M" msgstr "%x, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:194 #, no-c-format msgid "%x, %lโˆถ%M %p" msgstr "%x, %lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:197 #, no-c-format msgid "%b %d, %Hโˆถ%M" msgstr "%b %d, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:198 #, no-c-format msgid "%b %d, %lโˆถ%M %p" msgstr "%b %d, %lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:201 #, no-c-format msgid "%a, %Hโˆถ%M" msgstr "%a, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:202 #, no-c-format msgid "%a, %lโˆถ%M %p" msgstr "%a, %lโˆถ%M %p" #: main/src/ui/occupant_menu/list.vala:102 msgid "Owner" msgstr "Jabea" #: main/src/ui/occupant_menu/list.vala:104 msgid "Admin" msgstr "Administratzailea" #: main/src/ui/occupant_menu/list.vala:106 msgid "Member" msgstr "Kidea" #: main/src/ui/occupant_menu/list.vala:108 msgid "User" msgstr "Erabiltzailea" #: main/src/ui/occupant_menu/view.vala:29 #: main/src/ui/occupant_menu/view.vala:38 msgid "Invite" msgstr "Gonbidatu" #: main/src/ui/occupant_menu/view.vala:37 msgid "Invite to Conference" msgstr "Konferentziara gonbidatu" #: main/src/ui/occupant_menu/view.vala:88 msgid "Start private conversation" msgstr "Solasaldi pribatua hasi" #: main/src/ui/occupant_menu/view.vala:96 msgid "Kick" msgstr "Kanporatu" #: main/src/ui/global_search.vala:140 #, c-format msgid "%i search results" msgstr "Bilaketaren emaitzak: %i" #: main/src/ui/global_search.vala:167 #, c-format msgid "In %s" msgstr "hemen %s" #: main/src/ui/global_search.vala:167 #, c-format msgid "With %s" msgstr "%s(r)ekin" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Konferentziaren xehetasunak" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:24 msgid "Contact Details" msgstr "Kontaktuaren xehetasunak" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Gelaren izena" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Gelaren deskribapena" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Iraunkorra" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "Gelak iraun egingo du azken okupatzailea irten ondoren" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Publikoki bilagarria" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Okupatzaileek gaia alda dezakete" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Discover real JIDs" msgstr "Egiazko JIDak ikusi" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who may discover real JIDs?" msgstr "Nork ikus ditzake egiazko JIDak?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 msgid "Password" msgstr "Pasahitza" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "Password required for room entry, if any" msgstr "" "Gelan sartzeko beharrezko pasahitza, utzi zuriz pasahitzik ez ezartzeko" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderatua" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Ahotsa duten okupatzaileek soilik bidali dezakete mezuak" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Kideak soilik" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Kideak soilik sar daitezke gelara" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Mezuen historia" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Gelak itzuliko dituen gehienezko historiako mezuak" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Gelaren konfigurazioa" #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/src/ui/contact_details/settings_provider.vala:12 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Ezarpenak" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Blokeatu" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "" "Komunikazioa eta egoeraren eguneraketak edozein norabideetan blokeatuta daude" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Bertako ezarpenak" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Idazte jakinarazpenak bidali" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Bidali irakurtze baieztapena" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Jakinarazpenak" #: main/src/ui/contact_details/settings_provider.vala:82 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:124 msgid "On" msgstr "Piztuta" #: main/src/ui/contact_details/settings_provider.vala:84 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "Off" msgstr "Itzalita" #: main/src/ui/contact_details/settings_provider.vala:86 msgid "Only when mentioned" msgstr "Soilik aipatua izaterakoan" #: main/src/ui/contact_details/settings_provider.vala:88 #: main/src/ui/contact_details/settings_provider.vala:123 #, c-format msgid "Default: %s" msgstr "Lehenetsia: %s" #: main/src/ui/conversation_titlebar/search_entry.vala:11 msgid "Search messages" msgstr "Mezuak bilatu" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Kideak" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Mezu berri bat heltzerakoan jakinarazi" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Irrifartxoak emotikono bihurtu" #: main/data/manage_accounts/dialog.ui:9 main/data/menu_app.ui:7 msgid "Accounts" msgstr "Kontuak" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Bertako ezizena" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Ez dago konfiguratutako konturik" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Kontu bat gehitu" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Saioa hasi" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Kontua sortu" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Ezin izan da konexio seguru bat ezarri" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Konektatu" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Zerbitzari publiko bat hautatu" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Edo zerbitzari baten helbidea zehaztu" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Saioa hasi" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Beste zerbitzari bat hartu" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Guztia ezarri da!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Amaitu" #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Enkriptatu gabe" #: main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Laster-teklak" #: main/data/menu_app.ui:21 #, fuzzy msgid "About Dino" msgstr "Dino buruz" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "XMPP txat bezero modernoa" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino mahaigainerako iturburu irekiko txat bezero moderno bat da. Jabber/XMPP " "esperientzia garbi eta fidagarri bat ematen du zure pribatutasuna kontuan " "hartzeaz gain." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Amaieratik amaierarako enkriptazioa onartzen du OMEMO eta OpenPGPrekin eta " "pribatutasun ezaugarriak konfiguratzea baimentzen du irakurtze markak eta " "idazketa jakinarazpenak bezala." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dinok zerbitzaritik hartzen du historia eta beste gailuekin mezuak " "sinkronizatzen ditu." #: main/data/im.dino.Dino.appdata.xml.in:16 msgid "Main window with conversations" msgstr "Leiho nagusia elkarrizketekin" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Kontuak" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Ezizena" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Ezizena" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Kontaktua gehitu" #: main/data/shortcuts.ui:12 msgid "General" msgstr "Orokorra" #: main/data/shortcuts.ui:32 msgid "Navigation" msgstr "Nabigazioa" #: main/data/shortcuts.ui:37 msgid "Jump to next conversation" msgstr "" #: main/data/shortcuts.ui:44 msgid "Jump to previous conversation" msgstr "" #: main/data/global_search.ui:37 msgid "No active search" msgstr "Ez dago bilaketa aktiborik" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Idatzi bilatzen hasteko" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Ez dago bat egiten duen mezurik" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Ortografia egiaztatu edo saiatu iragazkiak kentzen" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "" #: main/data/unified_main_content.ui:147 msgid "You have no open chats" msgstr "" #~ msgid "Failed connecting to %s" #~ msgstr "Huts %s(e)ra konektatzerakoan" #~ msgid "Join Conference" #~ msgstr "Konferentziara batu" #~ msgid "File" #~ msgstr "Fitxategia" #~ msgid "Communicate happiness." #~ msgstr "Poztasuna komunikatu." #~ msgid "Preferences" #~ msgstr "Hobespenak" #~ msgid "Quit" #~ msgstr "Irten" #~ msgid "JID should be of the form โ€œuser@example.comโ€" #~ msgstr "JIDak \"erabiltzailea@eredua.com\" itxura izan beharko luke" #~ msgid "Copy Link Address" #~ msgstr "Loturaren helbidea kopiatu" #~ msgid "Copy" #~ msgstr "Kopiatu" #~ msgid "Select All" #~ msgstr "Dena hautatu" #~ msgid "Search" #~ msgstr "Bilatu" #~ msgid "Send message marker" #~ msgstr "Mezu marka bidali" #~ msgid "Start Chat" #~ msgstr "Berriketa hasi" #~ msgid "Request presence updates" #~ msgstr "Presentzia eguneraketak eskatu" #~ msgid "Join on startup" #~ msgstr "Hasieran batu" #~ msgid "Add Chat" #~ msgstr "Berriketa gehitu" dino-0.1.0/main/po/fi.po0000644000000000000000000005547013614354364013463 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2018-05-13 10:18+0000\n" "Language-Team: Finnish \n" "Language: fi\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.0-dev\n" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "Image sent" msgstr "" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "File sent" msgstr "" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "Image received" msgstr "" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "File received" msgstr "" #: main/src/ui/notifications.vala:98 msgid "Subscription request" msgstr "Yhteyspyyntรถ" #: main/src/ui/notifications.vala:105 main/src/ui/notifications.vala:140 #: main/src/ui/conversation_summary/subscription_notification.vala:37 msgid "Accept" msgstr "Hyvรคksy" #: main/src/ui/notifications.vala:106 main/src/ui/notifications.vala:139 #: main/src/ui/conversation_summary/subscription_notification.vala:38 msgid "Deny" msgstr "Hylkรครค" #: main/src/ui/notifications.vala:112 #: main/src/ui/manage_accounts/add_account_dialog.vala:258 #: main/src/ui/add_conversation/conference_details_fragment.vala:188 #, c-format msgid "Could not connect to %s" msgstr "" #: main/src/ui/notifications.vala:128 #, c-format msgid "Invitation to %s" msgstr "" #: main/src/ui/notifications.vala:129 #, c-format msgid "%s invited you to %s" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Poista tili %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Poista" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Valitse keskustelukuvake" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Valitse" #: main/src/ui/manage_accounts/dialog.vala:152 main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Peruuta" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Kuvat" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Kaikki tiedostot" #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connectingโ€ฆ" msgstr "Yhdistรครคโ€ฆ" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Yhdistetty" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Ei yhdistetty" #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Vรครคrรค salasana" #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "TLS-varmenne ei kelpaa" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Virhe" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Lisรครค tili" #: main/src/ui/manage_accounts/add_account_dialog.vala:166 #, c-format msgid "Sign in to %s" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:210 #, c-format msgid "You can now start using %s" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:263 #: main/src/ui/manage_accounts/add_account_dialog.vala:296 #: main/src/ui/manage_accounts/add_account_dialog.vala:306 #: main/src/ui/manage_accounts/add_account_dialog.vala:382 #: main/src/ui/add_conversation/conference_details_fragment.vala:191 msgid "Invalid address" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:278 msgid "Wrong username or password" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Something went wrong" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:321 msgid "No response from server" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 #, c-format msgid "Register on %s" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:330 msgid "The server requires to sign up through a website" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:332 msgid "Open Registration" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:344 msgid "Register" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:346 #, c-format msgid "Check %s for information on how to sign up" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:146 #: main/src/ui/conversation_selector/conversation_selector_row.vala:160 #: main/src/ui/util/helper.vala:136 main/src/ui/util/helper.vala:151 msgid "Me" msgstr "Minรค" #: main/src/ui/conversation_selector/conversation_selector_row.vala:258 #: main/src/ui/conversation_summary/date_separator_populator.vala:100 #, no-c-format msgid "%b %d" msgstr "%d. %b" #: main/src/ui/conversation_selector/conversation_selector_row.vala:262 #: main/src/ui/conversation_summary/date_separator_populator.vala:91 msgid "Yesterday" msgstr "Eilen" #: main/src/ui/conversation_selector/conversation_selector_row.vala:265 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:205 #, no-c-format msgid "%Hโˆถ%M" msgstr "%Hโˆถ%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:266 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:206 #, no-c-format msgid "%lโˆถ%M %p" msgstr "%lโˆถ%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:269 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:210 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i min sitten" msgstr[1] "%i min sitten" #: main/src/ui/conversation_selector/conversation_selector_row.vala:271 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:212 msgid "Just now" msgstr "Juuri nyt" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/unified_window.vala:249 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Liity kanavalle" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 msgid "Join" msgstr "Liity" #: main/src/ui/util/helper.vala:126 #, c-format msgid "%s from %s" msgstr "" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:28 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Lisรครค" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:42 msgid "Save" msgstr "Tallenna" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Seuraava" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/src/ui/unified_window.vala:248 #: main/data/conversation_list_titlebar_csd.ui:12 #: main/data/conversation_list_titlebar.ui:16 main/data/menu_add.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:20 main/data/shortcuts.ui:17 msgid "Start Conversation" msgstr "Aloita keskustelu" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Aloita" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joiningโ€ฆ" msgstr "Yhdistรครคโ€ฆ" #: main/src/ui/add_conversation/conference_details_fragment.vala:169 msgid "Password required to enter room" msgstr "Huoneeseen vaaditaan salasana" #: main/src/ui/add_conversation/conference_details_fragment.vala:174 msgid "Banned from joining or creating conference" msgstr "Estetty liittymรคstรค tai luomasta ryhmรคkeskustelua" #: main/src/ui/add_conversation/conference_details_fragment.vala:176 msgid "Room does not exist" msgstr "Huonetta ei ole olemassa" #: main/src/ui/add_conversation/conference_details_fragment.vala:178 msgid "Not allowed to create room" msgstr "Huoneen luomista ei sallita" #: main/src/ui/add_conversation/conference_details_fragment.vala:180 msgid "Members-only room" msgstr "Huone vain jรคsenille" #: main/src/ui/add_conversation/conference_details_fragment.vala:183 msgid "Choose a different nick" msgstr "Valitse jokin muu nimimerkki" #: main/src/ui/add_conversation/conference_details_fragment.vala:185 msgid "Too many occupants in room" msgstr "Liikaa kรคyttรคjiรค huoneessa" #: main/src/ui/unified_window.vala:227 msgid "Welcome to Dino!" msgstr "" #: main/src/ui/unified_window.vala:228 msgid "Sign in or create an account to get started." msgstr "" #: main/src/ui/unified_window.vala:229 msgid "Set up account" msgstr "" #: main/src/ui/unified_window.vala:237 msgid "No active accounts" msgstr "Ei aktiivisia tilejรค" #: main/src/ui/unified_window.vala:238 msgid "Manage accounts" msgstr "Hallitse tilejรค" #: main/src/ui/unified_window.vala:247 msgid "No active conversations" msgstr "Ei aktiivisia keskusteluja" #: main/src/ui/conversation_summary/file_widget.vala:275 #, c-format msgid "Downloading %sโ€ฆ" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:281 #, c-format msgid "%s offered: %s" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:283 #, c-format msgid "File offered: %s" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:285 msgid "File offered" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:290 msgid "File transfer failed" msgstr "" #: main/src/ui/conversation_summary/date_separator_populator.vala:85 msgid "Today" msgstr "Tรคnรครคn" #: main/src/ui/conversation_summary/date_separator_populator.vala:98 msgid "%a, %b %d" msgstr "%a, %b %d" #: main/src/ui/conversation_summary/chat_state_populator.vala:145 #, c-format msgid "%s, %s and %i others" msgstr "%s, %s and %i muuta" #: main/src/ui/conversation_summary/chat_state_populator.vala:147 #, c-format msgid "%s, %s and %s" msgstr "%s, %s ja %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:149 #, c-format msgid "%s and %s" msgstr "%s ja %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:154 msgid "is typingโ€ฆ" msgid_plural "are typingโ€ฆ" msgstr[0] "kirjoittaaโ€ฆ" msgstr[1] "kirjoittavatโ€ฆ" #: main/src/ui/conversation_summary/chat_state_populator.vala:156 msgid "has stopped typing" msgstr "on lakannut kirjoittamasta" #: main/src/ui/conversation_summary/content_item_widget_factory.vala:59 msgid "Message too long" msgstr "Viesti liian pitkรค" #: main/src/ui/conversation_summary/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Tรคmรค kรคyttรคjรค haluaisi lisรคtรค sinut yhteystietoihinsa" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:193 #, no-c-format msgid "%x, %Hโˆถ%M" msgstr "%x, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:194 #, no-c-format msgid "%x, %lโˆถ%M %p" msgstr "%x, %lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:197 #, no-c-format msgid "%b %d, %Hโˆถ%M" msgstr "%d. %b, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:198 #, no-c-format msgid "%b %d, %lโˆถ%M %p" msgstr "%d. %b, %lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:201 #, no-c-format msgid "%a, %Hโˆถ%M" msgstr "%a, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:202 #, no-c-format msgid "%a, %lโˆถ%M %p" msgstr "%a, %lโˆถ%M %p" #: main/src/ui/occupant_menu/list.vala:102 msgid "Owner" msgstr "Omistaja" #: main/src/ui/occupant_menu/list.vala:104 msgid "Admin" msgstr "Yllรคpitรคjรค" #: main/src/ui/occupant_menu/list.vala:106 msgid "Member" msgstr "Jรคsen" #: main/src/ui/occupant_menu/list.vala:108 msgid "User" msgstr "Kรคyttรคjรค" #: main/src/ui/occupant_menu/view.vala:29 #: main/src/ui/occupant_menu/view.vala:38 msgid "Invite" msgstr "Kutsu" #: main/src/ui/occupant_menu/view.vala:37 msgid "Invite to Conference" msgstr "Kutsu ryhmรคkeskusteluun" #: main/src/ui/occupant_menu/view.vala:88 msgid "Start private conversation" msgstr "Aloita yksityiskeskustelu" #: main/src/ui/occupant_menu/view.vala:96 msgid "Kick" msgstr "Potkaise pihalle" #: main/src/ui/global_search.vala:140 #, c-format msgid "%i search results" msgstr "" #: main/src/ui/global_search.vala:167 #, c-format msgid "In %s" msgstr "" #: main/src/ui/global_search.vala:167 #, c-format msgid "With %s" msgstr "" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Ryhmรคkeskustelun yksityiskohdat" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:24 msgid "Contact Details" msgstr "Yhteystiedot" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Huoneen nimi" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Huoneen kuvaus" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Pysyvรค" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "Huone sรคilyy viimeisen kรคyttรคjรคn poistuttua" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Julkisesti lรถydettรคvissรค" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Kรคyttรคjรคt voivat vaihtaa aihetta" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Discover real JIDs" msgstr "Nรคytรค oikeat JID:t" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who may discover real JIDs?" msgstr "Kuka voi nรคhdรค oikeat JID:t?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 msgid "Password" msgstr "Salasana" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "Password required for room entry, if any" msgstr "Huoneeseen vaadittava salasana, jos asetettu" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderoitu" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Vain รครคnen saaneet kรคyttรคjรคt voivat lรคhettรครค viestejรค" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Vain jรคsenille" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Vain jรคsenet pรครคsevรคt huoneeseen" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Viestihistoria" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Sรคilytettyjen viestien enimmรคismรครคrรค huoneessa" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Huoneen asetukset" #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/src/ui/contact_details/settings_provider.vala:12 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Asetukset" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Estรค" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "Viestit ja tilapรคivitykset estetรครคn molempiin suuntiin" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Paikalliset asetukset" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Lรคhetรค kirjoittamisilmoitukset" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Lรคhetรค lukukuittaukset" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Ilmoitukset" #: main/src/ui/contact_details/settings_provider.vala:82 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:124 msgid "On" msgstr "Kรคytรถssรค" #: main/src/ui/contact_details/settings_provider.vala:84 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "Off" msgstr "Pois" #: main/src/ui/contact_details/settings_provider.vala:86 msgid "Only when mentioned" msgstr "Vain mainittaessa" #: main/src/ui/contact_details/settings_provider.vala:88 #: main/src/ui/contact_details/settings_provider.vala:123 #, c-format msgid "Default: %s" msgstr "Oletus: %s" #: main/src/ui/conversation_titlebar/search_entry.vala:11 msgid "Search messages" msgstr "" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Ilmoita kun uusi viesti saapuu" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Muuta hymiรถt emojeiksi" #: main/data/manage_accounts/dialog.ui:9 main/data/menu_app.ui:7 msgid "Accounts" msgstr "Tilit" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Paikallinen alias" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Ei asetettuja tilejรค" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Lisรครค tili" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "" #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Salaamaton" #: main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "" #: main/data/menu_app.ui:21 msgid "About Dino" msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:8 #, fuzzy msgid "Modern XMPP Chat Client" msgstr "Moderni XMPP-asiakasohjelma" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino on nykyaikainen avoimen lรคhdekoodin jutteluohjelma tyรถpรถydรคlle. Se " "keskittyy tarjoamaan selkeรคn ja luotettavan Jabber/XMPP-kokemuksen " "unohtamatta yksityisyyttรคsi." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Se tukee pรครคstรค pรครคhรคn -salausta OMEMO:n ja OpenPGP:n avulla ja " "mahdollistaa yksityisyyteen liittyvien ominaisuuksien, kuten lukukuittausten " "ja kirjoitusilmoitusten asetusten mรครคrittรคmisen." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino hakee historian palvelimelta ja synkronisoi viestit muiden laitteiden " "kanssa." #: main/data/im.dino.Dino.appdata.xml.in:16 msgid "Main window with conversations" msgstr "Keskustelut pรครคikkunassa" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Tili" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Nimimerkki" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Alias" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Lisรครค yhteystieto" #: main/data/shortcuts.ui:12 msgid "General" msgstr "" #: main/data/shortcuts.ui:32 msgid "Navigation" msgstr "" #: main/data/shortcuts.ui:37 msgid "Jump to next conversation" msgstr "" #: main/data/shortcuts.ui:44 msgid "Jump to previous conversation" msgstr "" #: main/data/global_search.ui:37 msgid "No active search" msgstr "" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "" #: main/data/unified_main_content.ui:147 msgid "You have no open chats" msgstr "" #~ msgid "Join Conference" #~ msgstr "Liity ryhmรคkeskusteluun" #~ msgid "File" #~ msgstr "Tiedosto" #~ msgid "Preferences" #~ msgstr "Asetukset" #~ msgid "Quit" #~ msgstr "Lopeta" #~ msgid "JID should be of the form โ€œuser@example.comโ€" #~ msgstr "Anna JID muodossa \"user@example.com\"" #~ msgid "Copy Link Address" #~ msgstr "Kopioi linkin osoite" #~ msgid "Copy" #~ msgstr "Kopioi" #~ msgid "Select All" #~ msgstr "Valitse kaikki" #~ msgid "Search" #~ msgstr "Etsi" #~ msgid "Send message marker" #~ msgstr "Lรคhetรค lukukuittaus" #~ msgid "Start Chat" #~ msgstr "Aloita keskustelu" dino-0.1.0/main/po/fr.po0000644000000000000000000006145213614354364013471 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2020-01-07 00:21+0000\n" "Language-Team: French \n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" "X-Generator: Weblate 3.10\n" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "Image sent" msgstr "Image envoyรฉe" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "File sent" msgstr "Fichier envoyรฉ" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "Image received" msgstr "Image reรงue" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "File received" msgstr "Fichier reรงu" #: main/src/ui/notifications.vala:98 msgid "Subscription request" msgstr "Demande dโ€™abonnement" #: main/src/ui/notifications.vala:105 main/src/ui/notifications.vala:140 #: main/src/ui/conversation_summary/subscription_notification.vala:37 msgid "Accept" msgstr "Accepter" #: main/src/ui/notifications.vala:106 main/src/ui/notifications.vala:139 #: main/src/ui/conversation_summary/subscription_notification.vala:38 msgid "Deny" msgstr "Refuser" #: main/src/ui/notifications.vala:112 #: main/src/ui/manage_accounts/add_account_dialog.vala:258 #: main/src/ui/add_conversation/conference_details_fragment.vala:188 #, c-format msgid "Could not connect to %s" msgstr "N'a pas pu se connecter ร  %s" #: main/src/ui/notifications.vala:128 #, c-format msgid "Invitation to %s" msgstr "Invitation ร  %s" #: main/src/ui/notifications.vala:129 #, c-format msgid "%s invited you to %s" msgstr "%s vous a invitรฉยทe ร  %s" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Supprimer le compte %sโ€ฏ?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Supprimer" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Choisir un avatar" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Choisir" #: main/src/ui/manage_accounts/dialog.vala:152 main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Annuler" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Images" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Tous les fichiers" #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connectingโ€ฆ" msgstr "Connexionโ€ฆ" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Connectรฉ" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Dรฉconnectรฉ" #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Mauvais mot de passe" #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Certificat TLS invalide" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Erreur" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Ajouter un compte" #: main/src/ui/manage_accounts/add_account_dialog.vala:166 #, c-format msgid "Sign in to %s" msgstr "Se connecter ร  %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:210 #, c-format msgid "You can now start using %s" msgstr "Vous pouvez maintenant utiliser %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:263 #: main/src/ui/manage_accounts/add_account_dialog.vala:296 #: main/src/ui/manage_accounts/add_account_dialog.vala:306 #: main/src/ui/manage_accounts/add_account_dialog.vala:382 #: main/src/ui/add_conversation/conference_details_fragment.vala:191 msgid "Invalid address" msgstr "Adresse invalide" #: main/src/ui/manage_accounts/add_account_dialog.vala:278 msgid "Wrong username or password" msgstr "Nom d'utilisateur ou mot de passe incorrect" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Something went wrong" msgstr "Quelque chose s'est mal passรฉ" #: main/src/ui/manage_accounts/add_account_dialog.vala:321 msgid "No response from server" msgstr "Pas de rรฉponse du serveur" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 #, c-format msgid "Register on %s" msgstr "Sโ€™inscrire sur %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:330 msgid "The server requires to sign up through a website" msgstr "Ce serveur nรฉcessite de sโ€™inscrire depuis son site web" #: main/src/ui/manage_accounts/add_account_dialog.vala:332 msgid "Open Registration" msgstr "Inscription ouverte" #: main/src/ui/manage_accounts/add_account_dialog.vala:344 msgid "Register" msgstr "Sโ€™inscrire" #: main/src/ui/manage_accounts/add_account_dialog.vala:346 #, c-format msgid "Check %s for information on how to sign up" msgstr "Consultez %s pour plus dโ€™informations sur comment sโ€™inscrire" #: main/src/ui/conversation_selector/conversation_selector_row.vala:146 #: main/src/ui/conversation_selector/conversation_selector_row.vala:160 #: main/src/ui/util/helper.vala:136 main/src/ui/util/helper.vala:151 msgid "Me" msgstr "Moi" #: main/src/ui/conversation_selector/conversation_selector_row.vala:258 #: main/src/ui/conversation_summary/date_separator_populator.vala:100 #, no-c-format msgid "%b %d" msgstr "%d %b" #: main/src/ui/conversation_selector/conversation_selector_row.vala:262 #: main/src/ui/conversation_summary/date_separator_populator.vala:91 msgid "Yesterday" msgstr "Hier" #: main/src/ui/conversation_selector/conversation_selector_row.vala:265 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:205 #, no-c-format msgid "%Hโˆถ%M" msgstr "%Hh%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:266 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:206 #, no-c-format msgid "%lโˆถ%M %p" msgstr "%lโˆถ%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:269 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:210 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "Il y a %i minute" msgstr[1] "Il y a %i minutes" #: main/src/ui/conversation_selector/conversation_selector_row.vala:271 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:212 msgid "Just now" msgstr "ร€ lโ€™instant" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/unified_window.vala:249 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Rejoindre un salon" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 msgid "Join" msgstr "Rejoindre" #: main/src/ui/util/helper.vala:126 #, c-format msgid "%s from %s" msgstr "%s de %s" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:28 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Ajouter" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:42 msgid "Save" msgstr "Sauver" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Suivant" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Retour" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/src/ui/unified_window.vala:248 #: main/data/conversation_list_titlebar_csd.ui:12 #: main/data/conversation_list_titlebar.ui:16 main/data/menu_add.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:20 main/data/shortcuts.ui:17 msgid "Start Conversation" msgstr "Commencer une discussion" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Dรฉmarrer" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joiningโ€ฆ" msgstr "Connexion au salonโ€ฆ" #: main/src/ui/add_conversation/conference_details_fragment.vala:169 msgid "Password required to enter room" msgstr "Mot de passe nรฉcessaire pour rejoindre ce salon" #: main/src/ui/add_conversation/conference_details_fragment.vala:174 msgid "Banned from joining or creating conference" msgstr "Vous รชtes bannis pour la crรฉation ou connexion ร  ce salon" #: main/src/ui/add_conversation/conference_details_fragment.vala:176 msgid "Room does not exist" msgstr "Ce salon nโ€™existe pas" #: main/src/ui/add_conversation/conference_details_fragment.vala:178 msgid "Not allowed to create room" msgstr "Crรฉation de salon interdite" #: main/src/ui/add_conversation/conference_details_fragment.vala:180 msgid "Members-only room" msgstr "Salon rรฉservรฉ aux membres" #: main/src/ui/add_conversation/conference_details_fragment.vala:183 msgid "Choose a different nick" msgstr "Choisissez un autre pseudo" #: main/src/ui/add_conversation/conference_details_fragment.vala:185 msgid "Too many occupants in room" msgstr "Trop de participants dans ce salon" #: main/src/ui/unified_window.vala:227 msgid "Welcome to Dino!" msgstr "Bienvenue dans Dino !" #: main/src/ui/unified_window.vala:228 msgid "Sign in or create an account to get started." msgstr "Connectez-vous ou crรฉez un compte pour commencer." #: main/src/ui/unified_window.vala:229 msgid "Set up account" msgstr "Configurer le compte" #: main/src/ui/unified_window.vala:237 msgid "No active accounts" msgstr "Aucun compte actif" #: main/src/ui/unified_window.vala:238 msgid "Manage accounts" msgstr "Gรฉrer les comptes" #: main/src/ui/unified_window.vala:247 msgid "No active conversations" msgstr "Aucune discussion en cours" #: main/src/ui/conversation_summary/file_widget.vala:275 #, c-format msgid "Downloading %sโ€ฆ" msgstr "Tรฉlรฉchargement %sโ€ฆ" #: main/src/ui/conversation_summary/file_widget.vala:281 #, c-format msgid "%s offered: %s" msgstr "%s a offertย : %s" #: main/src/ui/conversation_summary/file_widget.vala:283 #, c-format msgid "File offered: %s" msgstr "Fichier offertย : %s" #: main/src/ui/conversation_summary/file_widget.vala:285 msgid "File offered" msgstr "Fichier offert" #: main/src/ui/conversation_summary/file_widget.vala:290 msgid "File transfer failed" msgstr "ร‰chec du transfert de fichier" #: main/src/ui/conversation_summary/date_separator_populator.vala:85 msgid "Today" msgstr "Aujourdโ€™hui" #: main/src/ui/conversation_summary/date_separator_populator.vala:98 msgid "%a, %b %d" msgstr "%a %d %b" #: main/src/ui/conversation_summary/chat_state_populator.vala:145 #, c-format msgid "%s, %s and %i others" msgstr "%s, %s et %i autres" #: main/src/ui/conversation_summary/chat_state_populator.vala:147 #, c-format msgid "%s, %s and %s" msgstr "%s, %s et %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:149 #, c-format msgid "%s and %s" msgstr "%s et %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:154 msgid "is typingโ€ฆ" msgid_plural "are typingโ€ฆ" msgstr[0] "est en train dโ€™รฉcrireโ€ฆ" msgstr[1] "sont en train dโ€™รฉcrireโ€ฆ" #: main/src/ui/conversation_summary/chat_state_populator.vala:156 msgid "has stopped typing" msgstr "a arrรชtรฉ dโ€™รฉcrire" #: main/src/ui/conversation_summary/content_item_widget_factory.vala:59 msgid "Message too long" msgstr "Message trop long" #: main/src/ui/conversation_summary/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Ce contact voudrait vous ajouter ร  sa liste de contacts" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:193 #, no-c-format msgid "%x, %Hโˆถ%M" msgstr "%x, %Hh%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:194 #, no-c-format msgid "%x, %lโˆถ%M %p" msgstr "%x, %lโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:197 #, no-c-format msgid "%b %d, %Hโˆถ%M" msgstr "%d %b, %Hh%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:198 #, no-c-format msgid "%b %d, %lโˆถ%M %p" msgstr "%d %b, %lโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:201 #, no-c-format msgid "%a, %Hโˆถ%M" msgstr "%a, %Hh%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:202 #, no-c-format msgid "%a, %lโˆถ%M %p" msgstr "%a, %lโˆถ%M" #: main/src/ui/occupant_menu/list.vala:102 msgid "Owner" msgstr "Propriรฉtaire" #: main/src/ui/occupant_menu/list.vala:104 msgid "Admin" msgstr "Administrateur" #: main/src/ui/occupant_menu/list.vala:106 msgid "Member" msgstr "Membre" #: main/src/ui/occupant_menu/list.vala:108 msgid "User" msgstr "Utilisateur" #: main/src/ui/occupant_menu/view.vala:29 #: main/src/ui/occupant_menu/view.vala:38 msgid "Invite" msgstr "Inviter" #: main/src/ui/occupant_menu/view.vala:37 msgid "Invite to Conference" msgstr "Inviter dans un salon" #: main/src/ui/occupant_menu/view.vala:88 msgid "Start private conversation" msgstr "Commencer une discussion privรฉe" #: main/src/ui/occupant_menu/view.vala:96 msgid "Kick" msgstr "ร‰jecter" #: main/src/ui/global_search.vala:140 #, c-format msgid "%i search results" msgstr "%i rรฉsultats de recherche" #: main/src/ui/global_search.vala:167 #, c-format msgid "In %s" msgstr "Dans %s" #: main/src/ui/global_search.vala:167 #, c-format msgid "With %s" msgstr "Avec %s" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Informations du salon" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:24 msgid "Contact Details" msgstr "Informations du contact" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Nom du salon" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Description du salon" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Persistant" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "Ce salon persistera aprรจs le dรฉpart du dernier occupant" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Visible publiquement" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Les occupants peuvent changer le sujet" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Discover real JIDs" msgstr "Dรฉcouverte des vrais JID" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who may discover real JIDs?" msgstr "Qui peut dรฉcouvrir les vrais JIDย ?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 msgid "Password" msgstr "Mot de passe" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "Password required for room entry, if any" msgstr "Mot de passe pour rejoindre le salon, vide pour aucun" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Modรฉrรฉ" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Seuls les occupants nโ€™รฉtant pas visiteurs peuvent envoyer des messages" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Seulement les membres" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Seuls les membres peuvent accรฉder au salon" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Historique des messages" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Nombre de messages dโ€™historique retournรฉ par le salon" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Configuration du salon" #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/src/ui/contact_details/settings_provider.vala:12 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Paramรจtres" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Bloquer" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "Tous les รฉchanges de messages et de statuts sont bloquรฉs" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Paramรจtres locaux" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Notifier vos correspondants lorsque vous รฉcrivez" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Envoyer les accusรฉs de rรฉception" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Notifications" #: main/src/ui/contact_details/settings_provider.vala:82 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:124 msgid "On" msgstr "Activรฉ" #: main/src/ui/contact_details/settings_provider.vala:84 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "Off" msgstr "Dรฉsactivรฉ" #: main/src/ui/contact_details/settings_provider.vala:86 msgid "Only when mentioned" msgstr "Seulement quand mentionnรฉ" #: main/src/ui/contact_details/settings_provider.vala:88 #: main/src/ui/contact_details/settings_provider.vala:123 #, c-format msgid "Default: %s" msgstr "Par dรฉfautโ€ฏ: %s" #: main/src/ui/conversation_titlebar/search_entry.vala:11 msgid "Search messages" msgstr "Rechercher des messages" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Membres" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Notifier lโ€™arrivรฉe de nouveaux messages" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Convertir les smileys en emojis" #: main/data/manage_accounts/dialog.ui:9 main/data/menu_app.ui:7 msgid "Accounts" msgstr "Comptes" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Alias local" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Aucun compte configurรฉ" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Ajouter un compte" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "S'identifier" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Crรฉer un compte" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "N'a pas pu รฉtablir une connexion sรฉcurisรฉe" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Connexion" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Choisissez un serveur public" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Ou indiquez lโ€™adresse dโ€™un serveur" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Connectez-vous ร  la place" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Choisissez un autre serveur" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Tout est prรชt !" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Terminer" #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Non-chiffrรฉ" #: main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Raccourcis clavier" #: main/data/menu_app.ui:21 msgid "About Dino" msgstr "ร€ propos de Dino" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Client XMPP moderne" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino est un client de chat libre et moderne pour le bureau. Il tente de " "fournir une expรฉrience Jabber/XMPP simple et fiable tout en ayant toujours ร  " "lโ€™esprit votre vie privรฉe." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Il prend en charge le chiffrement de bout en bout avec OMEMO et OpenPGP et " "permet de configurer les fonctions liรฉes ร  la vie privรฉe telles que lโ€™accusรฉ " "de rรฉception et les notifications de frappe." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino rรฉcupรจre lโ€™historique du serveur et synchronise les messages avec " "d'autres appareils." #: main/data/im.dino.Dino.appdata.xml.in:16 msgid "Main window with conversations" msgstr "Fenรชtre principale avec des conversations" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Compte" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Pseudo" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Alias" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Ajouter un contact" #: main/data/shortcuts.ui:12 msgid "General" msgstr "Gรฉnรฉral" #: main/data/shortcuts.ui:32 msgid "Navigation" msgstr "Navigation" #: main/data/shortcuts.ui:37 msgid "Jump to next conversation" msgstr "Allez ร  la discussion suivante" #: main/data/shortcuts.ui:44 msgid "Jump to previous conversation" msgstr "Revenir ร  la conversation prรฉcรฉdente" #: main/data/global_search.ui:37 msgid "No active search" msgstr "Pas de recherche en cours" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Commencez ร  รฉcrire pour lancer une recherche" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Aucun message correspondant" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Vรฉrifiez lโ€™orthographe ou essayez de supprimer des filtres" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "Cliquez ici pour commencer une discussion ou rejoindre un salon." #: main/data/unified_main_content.ui:147 msgid "You have no open chats" msgstr "Vous n'avez aucune discussion ouverte" #~ msgid "Failed connecting to %s" #~ msgstr "ร‰chec de la connexion ร  %s" #~ msgid "Join Conference" #~ msgstr "Rejoindre une confรฉrence" #~ msgid "File" #~ msgstr "Fichier" #~ msgid "Communicate happiness." #~ msgstr "Communiquer avec bonheur." #~ msgid "Preferences" #~ msgstr "Prรฉfรฉrences" #~ msgid "Quit" #~ msgstr "Quitter" #~ msgid "JID should be of the form โ€œuser@example.comโ€" #~ msgstr "Le JID doit รชtre de la forme ยซโ€ฏutilisateur@example.comโ€ฏยป" #~ msgid "Copy Link Address" #~ msgstr "Copier lโ€™adresse du lien" #~ msgid "Copy" #~ msgstr "Copier" #~ msgid "Select All" #~ msgstr "Tout sรฉlectionner" #~ msgid "Search" #~ msgstr "Rechercher" #~ msgid "Send message marker" #~ msgstr "Envoyer les marqueurs de message" #~ msgid "Start Chat" #~ msgstr "Commencer une discussion" #~ msgid "Request presence updates" #~ msgstr "Demander les mises ร  jour de la prรฉsence" #~ msgid "Join on startup" #~ msgstr "Rejoindre au dรฉmarrage" #~ msgid "Add Chat" #~ msgstr "Ajouter discussion" dino-0.1.0/main/po/gl.po0000644000000000000000000006003013614354364013453 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2020-01-05 08:21+0000\n" "Language-Team: Galician \n" "Language: gl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.10\n" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "Image sent" msgstr "Imaxe enviada" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "File sent" msgstr "Ficheiro enviado" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "Image received" msgstr "Imaxe recibida" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "File received" msgstr "Ficheiro recibido" #: main/src/ui/notifications.vala:98 msgid "Subscription request" msgstr "Solicitude de subscriciรณn" #: main/src/ui/notifications.vala:105 main/src/ui/notifications.vala:140 #: main/src/ui/conversation_summary/subscription_notification.vala:37 msgid "Accept" msgstr "Feito" #: main/src/ui/notifications.vala:106 main/src/ui/notifications.vala:139 #: main/src/ui/conversation_summary/subscription_notification.vala:38 msgid "Deny" msgstr "Rexeitar" #: main/src/ui/notifications.vala:112 #: main/src/ui/manage_accounts/add_account_dialog.vala:258 #: main/src/ui/add_conversation/conference_details_fragment.vala:188 #, c-format msgid "Could not connect to %s" msgstr "Non se conectou a %s" #: main/src/ui/notifications.vala:128 #, c-format msgid "Invitation to %s" msgstr "Convite a %s" #: main/src/ui/notifications.vala:129 #, c-format msgid "%s invited you to %s" msgstr "%s convidoute a %s" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Elimina-la conta %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Eliminar" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Escoller imaxe do perfil" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Escoller" #: main/src/ui/manage_accounts/dialog.vala:152 main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Cancelar" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Imaxes" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Tรณdolos ficheiros" #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connectingโ€ฆ" msgstr "Estase a conectarโ€ฆ" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Conectado" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Desconectado" #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Contrasinal incorrecto" #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Certificado do TLS non vรกlido" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Erro" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Engadir conta" #: main/src/ui/manage_accounts/add_account_dialog.vala:166 #, c-format msgid "Sign in to %s" msgstr "Conectar con %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:210 #, c-format msgid "You can now start using %s" msgstr "Agora xa pode comezar a utilizar %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:263 #: main/src/ui/manage_accounts/add_account_dialog.vala:296 #: main/src/ui/manage_accounts/add_account_dialog.vala:306 #: main/src/ui/manage_accounts/add_account_dialog.vala:382 #: main/src/ui/add_conversation/conference_details_fragment.vala:191 msgid "Invalid address" msgstr "Enderezo non vรกlido" #: main/src/ui/manage_accounts/add_account_dialog.vala:278 msgid "Wrong username or password" msgstr "Usuario ou contrasinal incorrectos" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Something went wrong" msgstr "Algo fallou" #: main/src/ui/manage_accounts/add_account_dialog.vala:321 msgid "No response from server" msgstr "Sen resposta do servidor" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 #, c-format msgid "Register on %s" msgstr "Rexistrarse en %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:330 msgid "The server requires to sign up through a website" msgstr "O servidor precisa que se rexistre a travรฉs dun sitio web" #: main/src/ui/manage_accounts/add_account_dialog.vala:332 msgid "Open Registration" msgstr "Rexistro aberto" #: main/src/ui/manage_accounts/add_account_dialog.vala:344 msgid "Register" msgstr "Rexistrar" #: main/src/ui/manage_accounts/add_account_dialog.vala:346 #, c-format msgid "Check %s for information on how to sign up" msgstr "Comprobe %s para mรกis informaciรณn sobre o rexistro" #: main/src/ui/conversation_selector/conversation_selector_row.vala:146 #: main/src/ui/conversation_selector/conversation_selector_row.vala:160 #: main/src/ui/util/helper.vala:136 main/src/ui/util/helper.vala:151 msgid "Me" msgstr "Eu" #: main/src/ui/conversation_selector/conversation_selector_row.vala:258 #: main/src/ui/conversation_summary/date_separator_populator.vala:100 #, no-c-format msgid "%b %d" msgstr "%d %b" #: main/src/ui/conversation_selector/conversation_selector_row.vala:262 #: main/src/ui/conversation_summary/date_separator_populator.vala:91 msgid "Yesterday" msgstr "Onte" #: main/src/ui/conversation_selector/conversation_selector_row.vala:265 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:205 #, no-c-format msgid "%Hโˆถ%M" msgstr "%Hโˆถ%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:266 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:206 #, no-c-format msgid "%lโˆถ%M %p" msgstr "%lโˆถ%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:269 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:210 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "hai %i min" msgstr[1] "hai %i minutos" #: main/src/ui/conversation_selector/conversation_selector_row.vala:271 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:212 msgid "Just now" msgstr "Xusto agora" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/unified_window.vala:249 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Unirse a unha canle" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 msgid "Join" msgstr "Unirse" #: main/src/ui/util/helper.vala:126 #, c-format msgid "%s from %s" msgstr "%s de %s" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:28 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Engadir" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:42 msgid "Save" msgstr "Gardar" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Seguinte" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Atrรกs" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/src/ui/unified_window.vala:248 #: main/data/conversation_list_titlebar_csd.ui:12 #: main/data/conversation_list_titlebar.ui:16 main/data/menu_add.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:20 main/data/shortcuts.ui:17 msgid "Start Conversation" msgstr "Comezar conversa" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Comezar" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joiningโ€ฆ" msgstr "Unรญndoseโ€ฆ" #: main/src/ui/add_conversation/conference_details_fragment.vala:169 msgid "Password required to enter room" msgstr "Precisa dun contrasinal para entrar na sala" #: main/src/ui/add_conversation/conference_details_fragment.vala:174 msgid "Banned from joining or creating conference" msgstr "Non ten permiso para unirse ou crea-lo grupo" #: main/src/ui/add_conversation/conference_details_fragment.vala:176 msgid "Room does not exist" msgstr "A sala non existe" #: main/src/ui/add_conversation/conference_details_fragment.vala:178 msgid "Not allowed to create room" msgstr "Non pode crea-la sala" #: main/src/ui/add_conversation/conference_details_fragment.vala:180 msgid "Members-only room" msgstr "Sala sรณ para membros" #: main/src/ui/add_conversation/conference_details_fragment.vala:183 msgid "Choose a different nick" msgstr "Escolla un alcume diferente" #: main/src/ui/add_conversation/conference_details_fragment.vala:185 msgid "Too many occupants in room" msgstr "Demasiada xente na sala" #: main/src/ui/unified_window.vala:227 msgid "Welcome to Dino!" msgstr "Benvida a Dino!" #: main/src/ui/unified_window.vala:228 msgid "Sign in or create an account to get started." msgstr "Conectarse ou crear unha conta para comezar." #: main/src/ui/unified_window.vala:229 msgid "Set up account" msgstr "Configurar conta" #: main/src/ui/unified_window.vala:237 msgid "No active accounts" msgstr "Sen contas activas" #: main/src/ui/unified_window.vala:238 msgid "Manage accounts" msgstr "Xestionar contas" #: main/src/ui/unified_window.vala:247 msgid "No active conversations" msgstr "Sen parolas activas" #: main/src/ui/conversation_summary/file_widget.vala:275 #, c-format msgid "Downloading %sโ€ฆ" msgstr "Descargando %sโ€ฆ" #: main/src/ui/conversation_summary/file_widget.vala:281 #, c-format msgid "%s offered: %s" msgstr "%s ofreceuche: %s" #: main/src/ui/conversation_summary/file_widget.vala:283 #, c-format msgid "File offered: %s" msgstr "Ficheiro ofrecido: %s" #: main/src/ui/conversation_summary/file_widget.vala:285 msgid "File offered" msgstr "Ficheiro ofrecido" #: main/src/ui/conversation_summary/file_widget.vala:290 msgid "File transfer failed" msgstr "Fallou a transferencia do ficheiro" #: main/src/ui/conversation_summary/date_separator_populator.vala:85 msgid "Today" msgstr "Hoxe" #: main/src/ui/conversation_summary/date_separator_populator.vala:98 msgid "%a, %b %d" msgstr "%a, %d %b" #: main/src/ui/conversation_summary/chat_state_populator.vala:145 #, c-format msgid "%s, %s and %i others" msgstr "%s, %s e %i outros" #: main/src/ui/conversation_summary/chat_state_populator.vala:147 #, c-format msgid "%s, %s and %s" msgstr "%s, %s e %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:149 #, c-format msgid "%s and %s" msgstr "%s e %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:154 msgid "is typingโ€ฆ" msgid_plural "are typingโ€ฆ" msgstr[0] "estรก a escribirโ€ฆ" msgstr[1] "estรกn a escribirโ€ฆ" #: main/src/ui/conversation_summary/chat_state_populator.vala:156 msgid "has stopped typing" msgstr "deixou de escribir" #: main/src/ui/conversation_summary/content_item_widget_factory.vala:59 msgid "Message too long" msgstr "Mensaxe demasiado longa" #: main/src/ui/conversation_summary/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Esta persoa gostarรญalle engadille รก sรบa listaxe dos contactos" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:193 #, no-c-format msgid "%x, %Hโˆถ%M" msgstr "%x, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:194 #, no-c-format msgid "%x, %lโˆถ%M %p" msgstr "%x, %lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:197 #, no-c-format msgid "%b %d, %Hโˆถ%M" msgstr "%d %b, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:198 #, no-c-format msgid "%b %d, %lโˆถ%M %p" msgstr "%d %b, %lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:201 #, no-c-format msgid "%a, %Hโˆถ%M" msgstr "%a, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:202 #, no-c-format msgid "%a, %lโˆถ%M %p" msgstr "%a, %lโˆถ%M %p" #: main/src/ui/occupant_menu/list.vala:102 msgid "Owner" msgstr "Dono" #: main/src/ui/occupant_menu/list.vala:104 msgid "Admin" msgstr "Administrador" #: main/src/ui/occupant_menu/list.vala:106 msgid "Member" msgstr "Membro" #: main/src/ui/occupant_menu/list.vala:108 msgid "User" msgstr "Usuario" #: main/src/ui/occupant_menu/view.vala:29 #: main/src/ui/occupant_menu/view.vala:38 msgid "Invite" msgstr "Convidar" #: main/src/ui/occupant_menu/view.vala:37 msgid "Invite to Conference" msgstr "Convidar รณ grupo" #: main/src/ui/occupant_menu/view.vala:88 msgid "Start private conversation" msgstr "Comezar conversa privada" #: main/src/ui/occupant_menu/view.vala:96 msgid "Kick" msgstr "Botar" #: main/src/ui/global_search.vala:140 #, c-format msgid "%i search results" msgstr "%i resultados da procura" #: main/src/ui/global_search.vala:167 #, c-format msgid "In %s" msgstr "En %s" #: main/src/ui/global_search.vala:167 #, c-format msgid "With %s" msgstr "Con %s" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Detalles do grupo" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:24 msgid "Contact Details" msgstr "Detalles do contacto" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Nome da sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Descriciรณn da sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Persistente" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "A sala permanecerรก despois de que saia o derradeiro participante" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Pรณdese atopar de xeito pรบblico" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Os participantes poden muda-lo asunto" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Discover real JIDs" msgstr "Descobri-los JIDs reais" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who may discover real JIDs?" msgstr "Quen pode descobri-los JIDs reais?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 msgid "Password" msgstr "Contrasinal" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "Password required for room entry, if any" msgstr "Contrasinal precisada para entrar na sala, se houbese" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderado" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Sรณ os participantes con voz poden enviar mensaxes" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Sรณ membros" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Sรณ os membros poden entrar na sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Histรณrico das mensaxes" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Morea mรกxima de rexistros fornecidos pola sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Axustes da sala" #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/src/ui/contact_details/settings_provider.vala:12 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Axustes" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Bloquear" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "" "As actualizaciรณns do estado e comunicaciรณn estรกn bloqueadas en calquera " "direcciรณn" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Axustes locais" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Enviar notificaciรณns รณ escribir" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Enviar confirmaciรณns de lectura" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Notificaciรณns" #: main/src/ui/contact_details/settings_provider.vala:82 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:124 msgid "On" msgstr "Habilitado" #: main/src/ui/contact_details/settings_provider.vala:84 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "Off" msgstr "Inhabilitado" #: main/src/ui/contact_details/settings_provider.vala:86 msgid "Only when mentioned" msgstr "Sรณ cando te mencionan" #: main/src/ui/contact_details/settings_provider.vala:88 #: main/src/ui/contact_details/settings_provider.vala:123 #, c-format msgid "Default: %s" msgstr "Por defecto: %s" #: main/src/ui/conversation_titlebar/search_entry.vala:11 msgid "Search messages" msgstr "Buscar mensaxes" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Membresรญa" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Notificar cando chega unha nova mensaxe" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Converter risoรฑos a emojis" #: main/data/manage_accounts/dialog.ui:9 main/data/menu_app.ui:7 msgid "Accounts" msgstr "Contas" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Alcume local" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Sen contas configuradas" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Engadir unha conta" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Conectar" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Crear unha conta" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Non se estableceu unha conexiรณn segura" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Conectar" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Escoller un servidor pรบblico" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Ou indicar un enderezo de servidor" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Iniciar sesiรณn no tocante diso" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Escoller outro servidor" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Todo feito!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Rematar" #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Non cifrado" #: main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Atallos de teclado" #: main/data/menu_app.ui:21 msgid "About Dino" msgstr "Acerca de Dino" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Cliente moderno para parolas XMPP" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino รฉ un cliente moderno e de cรณdigo aberto para o escritorio. Orientado a " "fornecer unha experiencia Jabber/XMPP limpa e fiรกbel tendo a privacidade e " "seguranza presentes." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Suporta o cifrado de punto-a-punto con OMEMO e OpenPGP e permite configurar " "trazos orientados รก privacidade tales coma confirmaciรณn de lectura e " "notificaciรณns de escritura." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino obtรฉn o histรณrico dende o servidor e sincroniza as mensaxes con outros " "dispositivos." #: main/data/im.dino.Dino.appdata.xml.in:16 msgid "Main window with conversations" msgstr "Lapela principal con parolas" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Conta" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Sobrenome" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Alcume" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Engadir contacto" #: main/data/shortcuts.ui:12 msgid "General" msgstr "Xeral" #: main/data/shortcuts.ui:32 msgid "Navigation" msgstr "Navegaciรณn" #: main/data/shortcuts.ui:37 msgid "Jump to next conversation" msgstr "Ir a seguinte conversa" #: main/data/shortcuts.ui:44 msgid "Jump to previous conversation" msgstr "Ir a conversa anterior" #: main/data/global_search.ui:37 msgid "No active search" msgstr "Sen procura activa" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Escriba para comezar unha procura" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Sen mensaxes coincidentes" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Comprobe a escrita ou tente eliminar filtros" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "Preme aquรญ para iniciar unha conversa ou unirte a unha canle." #: main/data/unified_main_content.ui:147 msgid "You have no open chats" msgstr "Non tes conversas abertas" #~ msgid "Failed connecting to %s" #~ msgstr "Ocorreu un erro รณ conectar cara %s" #~ msgid "Join Conference" #~ msgstr "Unirse a un grupo" #~ msgid "File" #~ msgstr "Ficheiro" #~ msgid "Communicate happiness." #~ msgstr "Espalla a felicidade." #~ msgid "Preferences" #~ msgstr "Preferencias" #~ msgid "Quit" #~ msgstr "Saรญr" #~ msgid "JID should be of the form โ€œuser@example.comโ€" #~ msgstr "O JID debe te-lo formato \"usuario@exemplo.com\"" #~ msgid "Copy Link Address" #~ msgstr "Copiar enderezo da ligazรณn" #~ msgid "Copy" #~ msgstr "Copiar" #~ msgid "Select All" #~ msgstr "Escoller todo" dino-0.1.0/main/po/hu.po0000644000000000000000000005641713614354364013503 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2019-12-21 12:21+0000\n" "Language-Team: Hungarian \n" "Language: hu\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.10\n" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "Image sent" msgstr "" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "File sent" msgstr "" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "Image received" msgstr "Kรฉp รฉrkezett" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "File received" msgstr "Fรกjl รฉrkezett" #: main/src/ui/notifications.vala:98 msgid "Subscription request" msgstr "Feliratkozรกsi kรฉrรฉs" #: main/src/ui/notifications.vala:105 main/src/ui/notifications.vala:140 #: main/src/ui/conversation_summary/subscription_notification.vala:37 msgid "Accept" msgstr "Elfogadรกs" #: main/src/ui/notifications.vala:106 main/src/ui/notifications.vala:139 #: main/src/ui/conversation_summary/subscription_notification.vala:38 msgid "Deny" msgstr "Tiltรกs" #: main/src/ui/notifications.vala:112 #: main/src/ui/manage_accounts/add_account_dialog.vala:258 #: main/src/ui/add_conversation/conference_details_fragment.vala:188 #, c-format msgid "Could not connect to %s" msgstr "" #: main/src/ui/notifications.vala:128 #, c-format msgid "Invitation to %s" msgstr "" #: main/src/ui/notifications.vala:129 #, c-format msgid "%s invited you to %s" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Eltรกvolรญtod a %s fiรณkot?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Eltรกvolรญtรกs" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Profilkรฉp vรกlasztรกsa" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Vรกlaszt" #: main/src/ui/manage_accounts/dialog.vala:152 main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Mรฉgse" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Kรฉpek" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "ร–sszes fรกjl" #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connectingโ€ฆ" msgstr "Csatlakozรกsโ€ฆ" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Csatlakozva" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Lecsatlakozva" #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Hibรกs jelszรณ" #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "ร‰rvรฉnytelen TLS tanรบsรญtvรกny" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Hiba" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Fiรณk hozzรกadรกsa" #: main/src/ui/manage_accounts/add_account_dialog.vala:166 #, c-format msgid "Sign in to %s" msgstr "Jelentkezzen be a %s-ba" #: main/src/ui/manage_accounts/add_account_dialog.vala:210 #, c-format msgid "You can now start using %s" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:263 #: main/src/ui/manage_accounts/add_account_dialog.vala:296 #: main/src/ui/manage_accounts/add_account_dialog.vala:306 #: main/src/ui/manage_accounts/add_account_dialog.vala:382 #: main/src/ui/add_conversation/conference_details_fragment.vala:191 msgid "Invalid address" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:278 msgid "Wrong username or password" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Something went wrong" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:321 msgid "No response from server" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 #, c-format msgid "Register on %s" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:330 msgid "The server requires to sign up through a website" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:332 msgid "Open Registration" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:344 msgid "Register" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:346 #, c-format msgid "Check %s for information on how to sign up" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:146 #: main/src/ui/conversation_selector/conversation_selector_row.vala:160 #: main/src/ui/util/helper.vala:136 main/src/ui/util/helper.vala:151 msgid "Me" msgstr "ร‰n" #: main/src/ui/conversation_selector/conversation_selector_row.vala:258 #: main/src/ui/conversation_summary/date_separator_populator.vala:100 #, no-c-format msgid "%b %d" msgstr "%b %d" #: main/src/ui/conversation_selector/conversation_selector_row.vala:262 #: main/src/ui/conversation_summary/date_separator_populator.vala:91 msgid "Yesterday" msgstr "Tegnap" #: main/src/ui/conversation_selector/conversation_selector_row.vala:265 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:205 #, no-c-format msgid "%Hโˆถ%M" msgstr "%H:%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:266 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:206 #, no-c-format msgid "%lโˆถ%M %p" msgstr "%I:%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:269 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:210 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i perccel ezelล‘tt" msgstr[1] "%i perccel ezelล‘tt" #: main/src/ui/conversation_selector/conversation_selector_row.vala:271 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:212 msgid "Just now" msgstr "Most" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/unified_window.vala:249 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Csatlakozรกs csatornรกhoz" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 msgid "Join" msgstr "Csatlakozรกs" #: main/src/ui/util/helper.vala:126 #, c-format msgid "%s from %s" msgstr "" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:28 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Hozzรกadรกs" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:42 msgid "Save" msgstr "Mentรฉs" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Kรถvetkezล‘" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/src/ui/unified_window.vala:248 #: main/data/conversation_list_titlebar_csd.ui:12 #: main/data/conversation_list_titlebar.ui:16 main/data/menu_add.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:20 main/data/shortcuts.ui:17 msgid "Start Conversation" msgstr "Csevegรฉs kezdemรฉnyezรฉse" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Indรญtรกs" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joiningโ€ฆ" msgstr "Csatlakozรกsโ€ฆ" #: main/src/ui/add_conversation/conference_details_fragment.vala:169 msgid "Password required to enter room" msgstr "A szobรกba valรณ belรฉpรฉshez jelszรณ szรผksรฉges" #: main/src/ui/add_conversation/conference_details_fragment.vala:174 msgid "Banned from joining or creating conference" msgstr "A csatlakozรกs egy konferenciรกhoz, vagy รบj lรฉtrehozรกsa le van tiltva" #: main/src/ui/add_conversation/conference_details_fragment.vala:176 msgid "Room does not exist" msgstr "A szoba nem lรฉtezik" #: main/src/ui/add_conversation/conference_details_fragment.vala:178 msgid "Not allowed to create room" msgstr "Szoba lรฉtrehozรกsa nem engedรฉlyezett" #: main/src/ui/add_conversation/conference_details_fragment.vala:180 msgid "Members-only room" msgstr "Csak tagoknak engedรฉlyezett szoba" #: main/src/ui/add_conversation/conference_details_fragment.vala:183 msgid "Choose a different nick" msgstr "Vรกlassz egy mรกsik becenevet" #: main/src/ui/add_conversation/conference_details_fragment.vala:185 msgid "Too many occupants in room" msgstr "Tรบl sok rรฉsztvevล‘ van a szobรกban" #: main/src/ui/unified_window.vala:227 msgid "Welcome to Dino!" msgstr "" #: main/src/ui/unified_window.vala:228 msgid "Sign in or create an account to get started." msgstr "" #: main/src/ui/unified_window.vala:229 msgid "Set up account" msgstr "" #: main/src/ui/unified_window.vala:237 msgid "No active accounts" msgstr "Nincs aktรญv fiรณk" #: main/src/ui/unified_window.vala:238 msgid "Manage accounts" msgstr "Fiรณkok kezelรฉse" #: main/src/ui/unified_window.vala:247 msgid "No active conversations" msgstr "Nincs aktรญv beszรฉlgetรฉs" #: main/src/ui/conversation_summary/file_widget.vala:275 #, c-format msgid "Downloading %sโ€ฆ" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:281 #, c-format msgid "%s offered: %s" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:283 #, c-format msgid "File offered: %s" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:285 msgid "File offered" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:290 msgid "File transfer failed" msgstr "" #: main/src/ui/conversation_summary/date_separator_populator.vala:85 msgid "Today" msgstr "Ma" #: main/src/ui/conversation_summary/date_separator_populator.vala:98 msgid "%a, %b %d" msgstr "%a, %b %d" #: main/src/ui/conversation_summary/chat_state_populator.vala:145 #, c-format msgid "%s, %s and %i others" msgstr "%s, %s รฉs %i tovรกbbi felhasznรกlรณ" #: main/src/ui/conversation_summary/chat_state_populator.vala:147 #, c-format msgid "%s, %s and %s" msgstr "%s, %s, รฉs %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:149 #, c-format msgid "%s and %s" msgstr "%s รฉs %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:154 msgid "is typingโ€ฆ" msgid_plural "are typingโ€ฆ" msgstr[0] "gรฉpelโ€ฆ" msgstr[1] "gรฉpelโ€ฆ" #: main/src/ui/conversation_summary/chat_state_populator.vala:156 msgid "has stopped typing" msgstr "abbahagyta a gรฉpelรฉst" #: main/src/ui/conversation_summary/content_item_widget_factory.vala:59 msgid "Message too long" msgstr "Az รผzenet tรบl hosszรบ" #: main/src/ui/conversation_summary/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Ez az ismerล‘s hozzรก szeretne adni tรฉged az ismerล‘slistรกjรกhoz" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:193 #, no-c-format msgid "%x, %Hโˆถ%M" msgstr "%x, %H:%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:194 #, no-c-format msgid "%x, %lโˆถ%M %p" msgstr "%x, %I:%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:197 #, no-c-format msgid "%b %d, %Hโˆถ%M" msgstr "%b %d, %H:%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:198 #, no-c-format msgid "%b %d, %lโˆถ%M %p" msgstr "%b %d, %I:%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:201 #, no-c-format msgid "%a, %Hโˆถ%M" msgstr "%a, %H:%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:202 #, no-c-format msgid "%a, %lโˆถ%M %p" msgstr "%a, %lโˆถ%M %p" #: main/src/ui/occupant_menu/list.vala:102 msgid "Owner" msgstr "Tulajdonos" #: main/src/ui/occupant_menu/list.vala:104 msgid "Admin" msgstr "Adminisztrรกtor" #: main/src/ui/occupant_menu/list.vala:106 msgid "Member" msgstr "Tag" #: main/src/ui/occupant_menu/list.vala:108 msgid "User" msgstr "Felhasznรกlรณ" #: main/src/ui/occupant_menu/view.vala:29 #: main/src/ui/occupant_menu/view.vala:38 msgid "Invite" msgstr "Meghรญvรกs" #: main/src/ui/occupant_menu/view.vala:37 msgid "Invite to Conference" msgstr "Meghรญvรกs a konferenciรกba" #: main/src/ui/occupant_menu/view.vala:88 msgid "Start private conversation" msgstr "Privรกt beszรฉlgetรฉs indรญtรกsa" #: main/src/ui/occupant_menu/view.vala:96 msgid "Kick" msgstr "Kirรบgรกs" #: main/src/ui/global_search.vala:140 #, c-format msgid "%i search results" msgstr "" #: main/src/ui/global_search.vala:167 #, c-format msgid "In %s" msgstr "" #: main/src/ui/global_search.vala:167 #, c-format msgid "With %s" msgstr "" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "A konferencia rรฉszletei" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:24 msgid "Contact Details" msgstr "Felhasznรกlรณ informรกciรณ" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "A szoba neve" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "A szoba leรญrรกsa" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Tartรณs" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "A szoba megmarad azutรกn is, hogy az utolsรณ rรฉsztvevล‘ elhagyta" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Nyilvรกnosan kereshetล‘" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Rรฉsztvevล‘k megvรกltoztathatjรกk a tรฉmรกt" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Discover real JIDs" msgstr "Valรณdi JID-k megtekintรฉse" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who may discover real JIDs?" msgstr "Ki lรกthatja a valรณdi JID-ket?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 msgid "Password" msgstr "Jelszรณ" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "Password required for room entry, if any" msgstr "A szobรกba lรฉpรฉshez szรผksรฉges jelszรณ, ha van" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderรกlt" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Csak az erre jogosult felhasznรกlรณk kรผldhetnek รผzenetet" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Csak tagoknak" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Csak a szoba tagjai lรฉphetnek be" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "รœzenet elล‘zmรฉnyek" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "A szoba รผzenetelล‘zmรฉnyeinek maximรกlis mennyisรฉge" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Szoba beรกllรญtรกsa" #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/src/ui/contact_details/settings_provider.vala:12 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Beรกllรญtรกsok" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Tiltรกs" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "" "Mind a kรฉt irรกnybรณl le van tiltva a kommunikรกciรณ รฉs az รกllapotjelentรฉsek" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Helyi beรกllรญtรกsok" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Gรฉpelรฉsi รฉrtesรญtรฉs kรผldรฉse" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Kรฉzbesรญtรฉsi jelentรฉs kรผldรฉse" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "ร‰rtesรญtรฉsek" #: main/src/ui/contact_details/settings_provider.vala:82 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:124 msgid "On" msgstr "Engedรฉlyezve" #: main/src/ui/contact_details/settings_provider.vala:84 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "Off" msgstr "Kikapcsolva" #: main/src/ui/contact_details/settings_provider.vala:86 msgid "Only when mentioned" msgstr "Csak ha emlรญtenek" #: main/src/ui/contact_details/settings_provider.vala:88 #: main/src/ui/contact_details/settings_provider.vala:123 #, c-format msgid "Default: %s" msgstr "Alapรฉrtelmezett: %s" #: main/src/ui/conversation_titlebar/search_entry.vala:11 msgid "Search messages" msgstr "" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "ร‰rtesรญtรฉs รบj รผzenet รฉrkezรฉsekor" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Smiley-k รกtalakรญtรกsa emojikkรก" #: main/data/manage_accounts/dialog.ui:9 main/data/menu_app.ui:7 msgid "Accounts" msgstr "Fiรณkok" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Helyi nรฉv" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Nincs beรกllรญtott fiรณk" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Fiรณk hozzรกadรกsa" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "" #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Titkosรญtatlan" #: main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Gyorsbillentyลฑk" #: main/data/menu_app.ui:21 #, fuzzy msgid "About Dino" msgstr "A Dino nรฉvjegye" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Modern XMPP รœzenetkรผldล‘" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "A Dino egy modern, nyรญlt forrรกskรณdรบ รผzenetkรผldล‘ alkalmazรกs asztali " "rendszerekre, ami a hangsรบlyt a letisztult รฉs megbรญzhatรณ Jabber/XMPP " "รฉlmรฉnyre helyezi, mikรถzben a magรกnszfรฉra megล‘rzรฉsรฉt is fontosnak tartja." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Tรกmogatja a vรฉgponttรณl-vรฉgpontig titkosรญtรกst az OMEMO รฉs az OpenPGP รกltal, " "รฉs magรกnszfรฉrรกhoz kรถtล‘dล‘ beรกllรญtรกsi lehetล‘sรฉgeket is biztosรญt, mint pรฉldรกul " "a kรฉzbesรญtรฉsi, vagy gรฉpelรฉsi รฉrtesรญtรฉsek kรผldรฉse." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "A Dino lekรฉri a chat elล‘zmรฉnyeket a szerverrล‘l, รฉs szinkronizรกlja az " "รผzeneteket a tรถbbi eszkรถzzel." #: main/data/im.dino.Dino.appdata.xml.in:16 msgid "Main window with conversations" msgstr "A fล‘ ablak a beszรฉlgetรฉsekkel" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Fiรณk" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Becenรฉv" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Alias" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Ismerล‘s hozzรกadรกsa" #: main/data/shortcuts.ui:12 msgid "General" msgstr "รltalรกnos" #: main/data/shortcuts.ui:32 msgid "Navigation" msgstr "Navigรกciรณ" #: main/data/shortcuts.ui:37 msgid "Jump to next conversation" msgstr "" #: main/data/shortcuts.ui:44 msgid "Jump to previous conversation" msgstr "" #: main/data/global_search.ui:37 msgid "No active search" msgstr "" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "" #: main/data/unified_main_content.ui:147 msgid "You have no open chats" msgstr "" #~ msgid "Join Conference" #~ msgstr "Csatlakozรกs konferenciรกhoz" #~ msgid "File" #~ msgstr "Fรกjl" #~ msgid "Preferences" #~ msgstr "Beรกllรญtรกsok" #~ msgid "Quit" #~ msgstr "Kilรฉpรฉs" #~ msgid "JID should be of the form โ€œuser@example.comโ€" #~ msgstr "" #~ "A JID-nek ebben a formรกtumban kell lennie: \"felhasznรกlรณ@szolgรกltatรณ.com\"" #~ msgid "Copy Link Address" #~ msgstr "Link cรญmรฉnek mรกsolรกsa" #~ msgid "Copy" #~ msgstr "Mรกsolรกs" #~ msgid "Select All" #~ msgstr "ร–sszes kijelรถlรฉse" #~ msgid "Search" #~ msgstr "Keresรฉs" dino-0.1.0/main/po/it.po0000644000000000000000000006125713614354364013501 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2019-12-21 12:21+0000\n" "Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.10\n" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "Image sent" msgstr "Immagine inviata" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "File sent" msgstr "File inviato" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "Image received" msgstr "Immagine ricevuta" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "File received" msgstr "File ricevuto" #: main/src/ui/notifications.vala:98 msgid "Subscription request" msgstr "Richiesta di iscrizione" #: main/src/ui/notifications.vala:105 main/src/ui/notifications.vala:140 #: main/src/ui/conversation_summary/subscription_notification.vala:37 msgid "Accept" msgstr "Accetta" #: main/src/ui/notifications.vala:106 main/src/ui/notifications.vala:139 #: main/src/ui/conversation_summary/subscription_notification.vala:38 msgid "Deny" msgstr "Rifiuta" #: main/src/ui/notifications.vala:112 #: main/src/ui/manage_accounts/add_account_dialog.vala:258 #: main/src/ui/add_conversation/conference_details_fragment.vala:188 #, c-format msgid "Could not connect to %s" msgstr "Impossibile connettersi a %s" #: main/src/ui/notifications.vala:128 #, c-format msgid "Invitation to %s" msgstr "Invito per %s" #: main/src/ui/notifications.vala:129 #, c-format msgid "%s invited you to %s" msgstr "%s ti ha invitato a %s" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Rimuovere l'account %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Rimuovi" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Scegli un avatar" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Seleziona" #: main/src/ui/manage_accounts/dialog.vala:152 main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Annulla" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Immagini" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Tutti i documenti" #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connectingโ€ฆ" msgstr "Connessioneโ€ฆ" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Connesso" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Disconnesso" #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Password errata" #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Il certificato TLS non รจ valido" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Errore" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Aggiungi un account" #: main/src/ui/manage_accounts/add_account_dialog.vala:166 #, c-format msgid "Sign in to %s" msgstr "Accedi a %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:210 #, c-format msgid "You can now start using %s" msgstr "Adesso puoi iniziare ad usare %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:263 #: main/src/ui/manage_accounts/add_account_dialog.vala:296 #: main/src/ui/manage_accounts/add_account_dialog.vala:306 #: main/src/ui/manage_accounts/add_account_dialog.vala:382 #: main/src/ui/add_conversation/conference_details_fragment.vala:191 msgid "Invalid address" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:278 msgid "Wrong username or password" msgstr "Utente o password errati" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Something went wrong" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:321 msgid "No response from server" msgstr "Nessuna risposta dal server" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 #, c-format msgid "Register on %s" msgstr "Registrati su %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:330 msgid "The server requires to sign up through a website" msgstr "Il server richiede di registrarsi tramite un sito web" #: main/src/ui/manage_accounts/add_account_dialog.vala:332 msgid "Open Registration" msgstr "Apri la Registrazione" #: main/src/ui/manage_accounts/add_account_dialog.vala:344 msgid "Register" msgstr "Registrati" #: main/src/ui/manage_accounts/add_account_dialog.vala:346 #, c-format msgid "Check %s for information on how to sign up" msgstr "Controlla %s per informazioni su come registrarsi" #: main/src/ui/conversation_selector/conversation_selector_row.vala:146 #: main/src/ui/conversation_selector/conversation_selector_row.vala:160 #: main/src/ui/util/helper.vala:136 main/src/ui/util/helper.vala:151 msgid "Me" msgstr "Io" #: main/src/ui/conversation_selector/conversation_selector_row.vala:258 #: main/src/ui/conversation_summary/date_separator_populator.vala:100 #, no-c-format msgid "%b %d" msgstr "%d %b" #: main/src/ui/conversation_selector/conversation_selector_row.vala:262 #: main/src/ui/conversation_summary/date_separator_populator.vala:91 msgid "Yesterday" msgstr "Ieri" #: main/src/ui/conversation_selector/conversation_selector_row.vala:265 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:205 #, no-c-format msgid "%Hโˆถ%M" msgstr "%Hโˆถ%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:266 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:206 #, no-c-format msgid "%lโˆถ%M %p" msgstr "%lโˆถ%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:269 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:210 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i minuto fa" msgstr[1] "%i minuti fa" #: main/src/ui/conversation_selector/conversation_selector_row.vala:271 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:212 msgid "Just now" msgstr "Adesso" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/unified_window.vala:249 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Entra nel Canale" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 msgid "Join" msgstr "Partecipa" #: main/src/ui/util/helper.vala:126 #, fuzzy, c-format msgid "%s from %s" msgstr "%s da %s" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:28 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Aggiungi" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:42 msgid "Save" msgstr "Salva" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Avanti" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Indietro" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/src/ui/unified_window.vala:248 #: main/data/conversation_list_titlebar_csd.ui:12 #: main/data/conversation_list_titlebar.ui:16 main/data/menu_add.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:20 main/data/shortcuts.ui:17 msgid "Start Conversation" msgstr "Inizia una Conversazione" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Inizia" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joiningโ€ฆ" msgstr "Accesso in corsoโ€ฆ" #: main/src/ui/add_conversation/conference_details_fragment.vala:169 msgid "Password required to enter room" msgstr "Una password รจ richiesta per entrare nella stanza" #: main/src/ui/add_conversation/conference_details_fragment.vala:174 msgid "Banned from joining or creating conference" msgstr "Ti รจ proibito entrare o creare la conferenza" #: main/src/ui/add_conversation/conference_details_fragment.vala:176 msgid "Room does not exist" msgstr "La stanza non esiste" #: main/src/ui/add_conversation/conference_details_fragment.vala:178 msgid "Not allowed to create room" msgstr "Non sei abilitato a creare la stanza" #: main/src/ui/add_conversation/conference_details_fragment.vala:180 msgid "Members-only room" msgstr "La stanza รจ solo per membri" #: main/src/ui/add_conversation/conference_details_fragment.vala:183 msgid "Choose a different nick" msgstr "Scegli un soprannome differente" #: main/src/ui/add_conversation/conference_details_fragment.vala:185 msgid "Too many occupants in room" msgstr "La stanza ha troppi occupanti" #: main/src/ui/unified_window.vala:227 msgid "Welcome to Dino!" msgstr "Benvenuto in Dino!" #: main/src/ui/unified_window.vala:228 msgid "Sign in or create an account to get started." msgstr "Accedi o crea un account per iniziare." #: main/src/ui/unified_window.vala:229 msgid "Set up account" msgstr "Configura account" #: main/src/ui/unified_window.vala:237 msgid "No active accounts" msgstr "Nessun account attivo" #: main/src/ui/unified_window.vala:238 msgid "Manage accounts" msgstr "Gestisci accounts" #: main/src/ui/unified_window.vala:247 msgid "No active conversations" msgstr "Nessuna conversazione attiva" #: main/src/ui/conversation_summary/file_widget.vala:275 #, c-format msgid "Downloading %sโ€ฆ" msgstr "Scaricamento di %s in corsoโ€ฆ" #: main/src/ui/conversation_summary/file_widget.vala:281 #, c-format msgid "%s offered: %s" msgstr "%s ha offerto: %s" #: main/src/ui/conversation_summary/file_widget.vala:283 #, c-format msgid "File offered: %s" msgstr "File offerto: %s" #: main/src/ui/conversation_summary/file_widget.vala:285 msgid "File offered" msgstr "File offerto" #: main/src/ui/conversation_summary/file_widget.vala:290 msgid "File transfer failed" msgstr "Trasferimento del file non riuscito" #: main/src/ui/conversation_summary/date_separator_populator.vala:85 msgid "Today" msgstr "Oggi" #: main/src/ui/conversation_summary/date_separator_populator.vala:98 msgid "%a, %b %d" msgstr "%a, %d %b" #: main/src/ui/conversation_summary/chat_state_populator.vala:145 #, c-format msgid "%s, %s and %i others" msgstr "%s, %s e %i altri" #: main/src/ui/conversation_summary/chat_state_populator.vala:147 #, c-format msgid "%s, %s and %s" msgstr "%s, %s, e %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:149 #, c-format msgid "%s and %s" msgstr "%s e %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:154 msgid "is typingโ€ฆ" msgid_plural "are typingโ€ฆ" msgstr[0] "sta scrivendoโ€ฆ" msgstr[1] "stanno scrivendoโ€ฆ" #: main/src/ui/conversation_summary/chat_state_populator.vala:156 msgid "has stopped typing" msgstr "ha smesso di scrivere" #: main/src/ui/conversation_summary/content_item_widget_factory.vala:59 msgid "Message too long" msgstr "Messaggio troppo lungo" #: main/src/ui/conversation_summary/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Questo contatto vorrebbe aggiungerti alla sua lista contatti" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:193 #, no-c-format msgid "%x, %Hโˆถ%M" msgstr "%x, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:194 #, no-c-format msgid "%x, %lโˆถ%M %p" msgstr "%x, %lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:197 #, no-c-format msgid "%b %d, %Hโˆถ%M" msgstr "%d %b, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:198 #, no-c-format msgid "%b %d, %lโˆถ%M %p" msgstr "%d %b, %lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:201 #, no-c-format msgid "%a, %Hโˆถ%M" msgstr "%a, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:202 #, no-c-format msgid "%a, %lโˆถ%M %p" msgstr "%a, %lโˆถ%M %p" #: main/src/ui/occupant_menu/list.vala:102 msgid "Owner" msgstr "Proprietario" #: main/src/ui/occupant_menu/list.vala:104 msgid "Admin" msgstr "Amministratore" #: main/src/ui/occupant_menu/list.vala:106 msgid "Member" msgstr "Membro" #: main/src/ui/occupant_menu/list.vala:108 msgid "User" msgstr "Utente" #: main/src/ui/occupant_menu/view.vala:29 #: main/src/ui/occupant_menu/view.vala:38 msgid "Invite" msgstr "Invita" #: main/src/ui/occupant_menu/view.vala:37 msgid "Invite to Conference" msgstr "Invita alla conferenza" #: main/src/ui/occupant_menu/view.vala:88 msgid "Start private conversation" msgstr "Inizia una conversazione privata" #: main/src/ui/occupant_menu/view.vala:96 msgid "Kick" msgstr "Espelli" #: main/src/ui/global_search.vala:140 #, c-format msgid "%i search results" msgstr "%i risultati per la ricerca" #: main/src/ui/global_search.vala:167 #, c-format msgid "In %s" msgstr "In %s" #: main/src/ui/global_search.vala:167 #, c-format msgid "With %s" msgstr "Con %s" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Dettagli della conferenza" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:24 msgid "Contact Details" msgstr "Dettagli del contatto" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Nome della stanza" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Descrizione della stanza" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Persistente" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "La stanza continuerร  ad esistere dopo l'uscita dell'ultimo occupante" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Ricercabile pubblicamente" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Gli occupanti possono cambiare l'argomento" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Discover real JIDs" msgstr "Scopri i veri JIDs" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who may discover real JIDs?" msgstr "Chi puรฒ scoprire i veri JIDs?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 msgid "Password" msgstr "Password" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "Password required for room entry, if any" msgstr "Password richiesta per entrare nella stanza, se impostata" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderata" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Solo i partecipanti autorizzati possono mandare messaggi" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Solo per membri" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Solo i membri possono entrare nella stanza" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Cronologia dei messaggi" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Numero massimo di messaggi in cronologia restituiti dalla stanza" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Configurazione della stanza" #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/src/ui/contact_details/settings_provider.vala:12 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Impostazioni" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Blocca" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "" "La comunicazione e gli aggiornamenti di stato sono bloccati in entrambe le " "direzioni" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Impostazioni locali" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Invia notifiche di digitazione" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Invia ricevute di lettura" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Notifiche" #: main/src/ui/contact_details/settings_provider.vala:82 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:124 msgid "On" msgstr "Sรฌ" #: main/src/ui/contact_details/settings_provider.vala:84 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "Off" msgstr "No" #: main/src/ui/contact_details/settings_provider.vala:86 msgid "Only when mentioned" msgstr "Solo se menzionato" #: main/src/ui/contact_details/settings_provider.vala:88 #: main/src/ui/contact_details/settings_provider.vala:123 #, c-format msgid "Default: %s" msgstr "Default: %s" #: main/src/ui/conversation_titlebar/search_entry.vala:11 msgid "Search messages" msgstr "Cerca tra i messaggi" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Partecipanti" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Notifica quando arriva un nuovo messaggio" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Trasforma gli smile in emoji" #: main/data/manage_accounts/dialog.ui:9 main/data/menu_app.ui:7 msgid "Accounts" msgstr "Account" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Alias locale" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Nessun account configurato" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Aggiungi un account" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Accedi" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Crea account" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Impossibile stabilire una connessione sicura" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Connetti" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Scegli un server pubblico" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Oppure inserisci l'indirizzo di un server" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Altrimenti accedi" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Scegli un altro server" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Tutto pronto!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Fine" #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Non cifrato" #: main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Scorciatoie da tastiera" #: main/data/menu_app.ui:21 msgid "About Dino" msgstr "Informazioni su Dino" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Client di chat moderno per XMPP" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino รจ un client di chat per il desktop, moderno e open-source. Si concentra " "nel fornire un'esperienza Jabber/XMPP pulita e affidabile tenendo presente " "la tua privacy." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Support la crittografia end-to-end tramite OMEMO e OpenPGP e permette di " "configurare le funzioni relative alla privacy come le ricevute di lettura e " "le notifiche di digitazione." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino recupera la cronologia dal server e sincronizza i messaggi con gli " "altri dispositivi." #: main/data/im.dino.Dino.appdata.xml.in:16 msgid "Main window with conversations" msgstr "La finestra principale con le conversazioni" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Account" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Soprannome" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Alias" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Aggiungi Contatto" #: main/data/shortcuts.ui:12 msgid "General" msgstr "Generali" #: main/data/shortcuts.ui:32 msgid "Navigation" msgstr "Navigazione" #: main/data/shortcuts.ui:37 msgid "Jump to next conversation" msgstr "Passare alla conversazione successiva" #: main/data/shortcuts.ui:44 msgid "Jump to previous conversation" msgstr "Passare alla conversazione precedente" #: main/data/global_search.ui:37 msgid "No active search" msgstr "Nessuna ricerca attiva" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Digita per iniziare una ricerca" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Nessun messaggio corrispondente" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Controlla l'ortografia o prova a rimuovere dei filtri" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "" "Fai clic qui per iniziare una conversazione o per entrare in un canale." #: main/data/unified_main_content.ui:147 msgid "You have no open chats" msgstr "Non hai chat aperte" #~ msgid "Failed connecting to %s" #~ msgstr "Connessione a %s fallita" #~ msgid "Join Conference" #~ msgstr "Partecipa alla conferenza" #~ msgid "File" #~ msgstr "File" #~ msgid "Communicate happiness." #~ msgstr "Comunica felicitร ." #~ msgid "Preferences" #~ msgstr "Preferenze" #~ msgid "Quit" #~ msgstr "Esci" #~ msgid "JID should be of the form โ€œuser@example.comโ€" #~ msgstr "Il JID dovrebbe essere nella forma \"utente@example.com\"" #~ msgid "Copy Link Address" #~ msgstr "Copia l'indirizzo del collegamento" #~ msgid "Copy" #~ msgstr "Copia" #~ msgid "Select All" #~ msgstr "Seleziona Tutto" #~ msgid "Search" #~ msgstr "Cerca" #~ msgid "Send message marker" #~ msgstr "Invia lo stato del messaggio" #~ msgid "Start Chat" #~ msgstr "Inizia una conversazione" #~ msgid "Request presence updates" #~ msgstr "Richiedi gli aggiornamenti di stato" #~ msgid "This is an app-notification. Click the button to dismiss" #~ msgstr "Questa รจ una notifica dell'app. Clicca il bottone per chiudere" #~ msgid "Join on startup" #~ msgstr "Entra all'avvio" #~ msgid "Add Chat" #~ msgstr "Aggiungi conversazione" dino-0.1.0/main/po/ja.po0000644000000000000000000006260613614354364013456 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2020-01-28 23:24+0000\n" "Language-Team: Japanese \n" "Language: ja\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 3.11-dev\n" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "Image sent" msgstr "็”ปๅƒใ‚’้€ไฟกใ—ใพใ—ใŸ" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "File sent" msgstr "ใƒ•ใ‚กใ‚คใƒซใ‚’้€ไฟกใ—ใพใ—ใŸ" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "Image received" msgstr "็”ปๅƒใ‚’ๅ—ไฟกใ—ใพใ—ใŸ" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "File received" msgstr "ใƒ•ใ‚กใ‚คใƒซใ‚’ๅ—ไฟกใ—ใพใ—ใŸ" #: main/src/ui/notifications.vala:98 msgid "Subscription request" msgstr "ๅœจๅธญ้€š็Ÿฅใฎ็”ณ่พผ" #: main/src/ui/notifications.vala:105 main/src/ui/notifications.vala:140 #: main/src/ui/conversation_summary/subscription_notification.vala:37 msgid "Accept" msgstr "ๆ‰ฟ่ซพ" #: main/src/ui/notifications.vala:106 main/src/ui/notifications.vala:139 #: main/src/ui/conversation_summary/subscription_notification.vala:38 msgid "Deny" msgstr "ๆ‹’ๅฆ" #: main/src/ui/notifications.vala:112 #: main/src/ui/manage_accounts/add_account_dialog.vala:258 #: main/src/ui/add_conversation/conference_details_fragment.vala:188 #, c-format msgid "Could not connect to %s" msgstr "%s ใซๆŽฅ็ถšใงใใพใ›ใ‚“ใงใ—ใŸ" #: main/src/ui/notifications.vala:128 #, c-format msgid "Invitation to %s" msgstr "%s ใธใฎๆ‹›ๅพ…" #: main/src/ui/notifications.vala:129 #, c-format msgid "%s invited you to %s" msgstr "%s ใ•ใ‚“ใŒใ‚ใชใŸใ‚’ %s ใซๆ‹›ๅพ…ใ—ใพใ—ใŸ" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "ใ‚ขใ‚ซใ‚ฆใƒณใƒˆ %s ใ‚’ๅ‰Š้™คใ—ใพใ™ใ‹๏ผŸ" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "ๅ‰Š้™ค" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "ใ‚ขใƒใ‚ฟใƒผใ‚’้ธๆŠž" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "้ธๆŠž" #: main/src/ui/manage_accounts/dialog.vala:152 main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "ใ‚ญใƒฃใƒณใ‚ปใƒซ" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "็”ปๅƒ" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "ใ™ในใฆใฎใƒ•ใ‚กใ‚คใƒซ" #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connectingโ€ฆ" msgstr "ๆŽฅ็ถš่ฉฆ่กŒไธญโ€ฆ" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "ๆŽฅ็ถšๆธˆใฟ" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "ๅˆ‡ๆ–ญ" #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "ใƒ‘ใ‚นใƒฏใƒผใƒ‰ใŒ้•ใ„ใพใ™" #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "TLS ่จผๆ˜Žๆ›ธใŒไธๆญฃใงใ™" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "ใ‚จใƒฉใƒผ" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "ใ‚ขใ‚ซใ‚ฆใƒณใƒˆใ‚’่ฟฝๅŠ " #: main/src/ui/manage_accounts/add_account_dialog.vala:166 #, c-format msgid "Sign in to %s" msgstr "%s ใซใ‚ตใ‚คใƒณใ‚คใƒณ" #: main/src/ui/manage_accounts/add_account_dialog.vala:210 #, c-format msgid "You can now start using %s" msgstr "%s ใ‚’ใŠไฝฟใ„ใ„ใŸใ ใ‘ใพใ™" #: main/src/ui/manage_accounts/add_account_dialog.vala:263 #: main/src/ui/manage_accounts/add_account_dialog.vala:296 #: main/src/ui/manage_accounts/add_account_dialog.vala:306 #: main/src/ui/manage_accounts/add_account_dialog.vala:382 #: main/src/ui/add_conversation/conference_details_fragment.vala:191 msgid "Invalid address" msgstr "ใ‚ขใƒ‰ใƒฌใ‚นใŒไธๆญฃใงใ™" #: main/src/ui/manage_accounts/add_account_dialog.vala:278 msgid "Wrong username or password" msgstr "ใƒฆใƒผใ‚ถใƒผๅใพใŸใฏใƒ‘ใ‚นใƒฏใƒผใƒ‰ใŒ้•ใ„ใพใ™" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Something went wrong" msgstr "ๅ•้กŒใŒ็™บ็”Ÿใ—ใพใ—ใŸ" #: main/src/ui/manage_accounts/add_account_dialog.vala:321 msgid "No response from server" msgstr "ใ‚ตใƒผใƒใƒผใ‹ใ‚‰ๅฟœ็ญ”ใŒใ‚ใ‚Šใพใ›ใ‚“" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 #, c-format msgid "Register on %s" msgstr "%s ใซ็™ป้Œฒ" #: main/src/ui/manage_accounts/add_account_dialog.vala:330 msgid "The server requires to sign up through a website" msgstr "ใ‚ตใƒผใƒใƒผใŒ Web ใ‚ตใ‚คใƒˆใงใฎใ‚ตใ‚คใƒณใ‚ขใƒƒใƒ—ใ‚’่ฆๆฑ‚ใ—ใฆใ„ใพใ™" #: main/src/ui/manage_accounts/add_account_dialog.vala:332 msgid "Open Registration" msgstr "็™ป้Œฒใ‚’้–‹ใ" #: main/src/ui/manage_accounts/add_account_dialog.vala:344 msgid "Register" msgstr "็™ป้Œฒ" #: main/src/ui/manage_accounts/add_account_dialog.vala:346 #, c-format msgid "Check %s for information on how to sign up" msgstr "ใ‚ตใ‚คใƒณใ‚ขใƒƒใƒ—ใฎๆ–นๆณ•ใซ้–ขใ™ใ‚‹ๆƒ…ๅ ฑใฏใ€%s ใ‚’ใ”็ขบ่ชใใ ใ•ใ„" #: main/src/ui/conversation_selector/conversation_selector_row.vala:146 #: main/src/ui/conversation_selector/conversation_selector_row.vala:160 #: main/src/ui/util/helper.vala:136 main/src/ui/util/helper.vala:151 msgid "Me" msgstr "่‡ชๅˆ†" #: main/src/ui/conversation_selector/conversation_selector_row.vala:258 #: main/src/ui/conversation_summary/date_separator_populator.vala:100 #, no-c-format msgid "%b %d" msgstr "%b%dๆ—ฅ" #: main/src/ui/conversation_selector/conversation_selector_row.vala:262 #: main/src/ui/conversation_summary/date_separator_populator.vala:91 msgid "Yesterday" msgstr "ๆ˜จๆ—ฅ" #: main/src/ui/conversation_selector/conversation_selector_row.vala:265 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:205 #, no-c-format msgid "%Hโˆถ%M" msgstr "%Hโˆถ%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:266 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:206 #, no-c-format msgid "%lโˆถ%M %p" msgstr "%p %lโˆถ%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:269 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:210 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i ๅˆ†ๅ‰" #: main/src/ui/conversation_selector/conversation_selector_row.vala:271 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:212 msgid "Just now" msgstr "ใŸใฃใŸไปŠ" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/unified_window.vala:249 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "ใƒใƒฃใƒณใƒใƒซใซๅ‚ๅŠ " #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 msgid "Join" msgstr "ๅ‚ๅŠ " #: main/src/ui/util/helper.vala:126 #, c-format msgid "%s from %s" msgstr "%2$s ใฎ %1$s" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:28 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "่ฟฝๅŠ " #: main/src/ui/add_conversation/add_groupchat_dialog.vala:42 msgid "Save" msgstr "ไฟๅญ˜" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "ๆฌกใธ" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "ๆˆปใ‚‹" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/src/ui/unified_window.vala:248 #: main/data/conversation_list_titlebar_csd.ui:12 #: main/data/conversation_list_titlebar.ui:16 main/data/menu_add.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:20 main/data/shortcuts.ui:17 msgid "Start Conversation" msgstr "ใƒˆใƒผใ‚ฏใ‚’้–‹ๅง‹" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "้–‹ๅง‹" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joiningโ€ฆ" msgstr "ๅ‚ๅŠ ่ฉฆ่กŒไธญโ€ฆ" #: main/src/ui/add_conversation/conference_details_fragment.vala:169 msgid "Password required to enter room" msgstr "ใƒˆใƒผใ‚ฏใƒซใƒผใƒ ใซๅ‚ๅŠ ใ™ใ‚‹ใซใฏใƒ‘ใ‚นใƒฏใƒผใƒ‰ใŒๅฟ…่ฆใงใ™" #: main/src/ui/add_conversation/conference_details_fragment.vala:174 msgid "Banned from joining or creating conference" msgstr "ๅ‚ๅŠ ไธญใพใŸใฏไฝœๆˆไธญใฎใƒˆใƒผใ‚ฏใƒซใƒผใƒ ใ‹ใ‚‰้€€ไผšใ•ใ›ใ‚‰ใ‚Œใพใ—ใŸ" #: main/src/ui/add_conversation/conference_details_fragment.vala:176 msgid "Room does not exist" msgstr "ใƒˆใƒผใ‚ฏใƒซใƒผใƒ ใฏๅญ˜ๅœจใ—ใพใ›ใ‚“" #: main/src/ui/add_conversation/conference_details_fragment.vala:178 msgid "Not allowed to create room" msgstr "ใƒˆใƒผใ‚ฏใƒซใƒผใƒ ใ‚’ไฝœๆˆใ™ใ‚‹ๆจฉ้™ใŒใ‚ใ‚Šใพใ›ใ‚“" #: main/src/ui/add_conversation/conference_details_fragment.vala:180 msgid "Members-only room" msgstr "ใƒกใƒณใƒใƒผๅˆถใƒˆใƒผใ‚ฏใƒซใƒผใƒ " #: main/src/ui/add_conversation/conference_details_fragment.vala:183 msgid "Choose a different nick" msgstr "ๅˆฅใฎใƒ‹ใƒƒใ‚ฏใƒใƒผใƒ ใ‚’้ธใ‚“ใงใใ ใ•ใ„" #: main/src/ui/add_conversation/conference_details_fragment.vala:185 msgid "Too many occupants in room" msgstr "ใƒˆใƒผใ‚ฏใƒซใƒผใƒ ใฎๅ‚ๅŠ ่€…ใŒๅคšใ™ใŽใพใ™" #: main/src/ui/unified_window.vala:227 msgid "Welcome to Dino!" msgstr "Dino ใธใ‚ˆใ†ใ“ใ๏ผ" #: main/src/ui/unified_window.vala:228 msgid "Sign in or create an account to get started." msgstr "ใ‚ตใ‚คใƒณใ‚คใƒณใพใŸใฏใ‚ขใ‚ซใ‚ฆใƒณใƒˆ็™ป้Œฒใ‚’ใ—ใฆใใ ใ•ใ„ใ€‚" #: main/src/ui/unified_window.vala:229 msgid "Set up account" msgstr "ใ‚ขใ‚ซใ‚ฆใƒณใƒˆใ‚’ใ‚ปใƒƒใƒˆใ‚ขใƒƒใƒ—" #: main/src/ui/unified_window.vala:237 msgid "No active accounts" msgstr "ใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–ใชใ‚ขใ‚ซใ‚ฆใƒณใƒˆใŒใ‚ใ‚Šใพใ›ใ‚“" #: main/src/ui/unified_window.vala:238 msgid "Manage accounts" msgstr "ใ‚ขใ‚ซใ‚ฆใƒณใƒˆใ‚’็ฎก็†" #: main/src/ui/unified_window.vala:247 msgid "No active conversations" msgstr "ใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–ใชใƒˆใƒผใ‚ฏใŒใ‚ใ‚Šใพใ›ใ‚“" #: main/src/ui/conversation_summary/file_widget.vala:275 #, c-format msgid "Downloading %sโ€ฆ" msgstr "%s ใ‚’ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ใ—ใฆใ„ใพใ™โ€ฆ" #: main/src/ui/conversation_summary/file_widget.vala:281 #, fuzzy, c-format msgid "%s offered: %s" msgstr "%s ใ‹ใ‚‰ๆไพ›ใ•ใ‚Œใฆใ„ใ‚‹ใƒ•ใ‚กใ‚คใƒซ: %s" #: main/src/ui/conversation_summary/file_widget.vala:283 #, fuzzy, c-format msgid "File offered: %s" msgstr "ๆไพ›ใ•ใ‚Œใฆใ„ใ‚‹ใƒ•ใ‚กใ‚คใƒซ: %s" #: main/src/ui/conversation_summary/file_widget.vala:285 msgid "File offered" msgstr "ใƒ•ใ‚กใ‚คใƒซใŒๆไพ›ใ•ใ‚Œใฆใ„ใพใ™" #: main/src/ui/conversation_summary/file_widget.vala:290 msgid "File transfer failed" msgstr "ใƒ•ใ‚กใ‚คใƒซใฎ่ปข้€ใซๅคฑๆ•—ใ—ใพใ—ใŸ" #: main/src/ui/conversation_summary/date_separator_populator.vala:85 msgid "Today" msgstr "ไปŠๆ—ฅ" #: main/src/ui/conversation_summary/date_separator_populator.vala:98 msgid "%a, %b %d" msgstr "%b%dๆ—ฅ (%a)" #: main/src/ui/conversation_summary/chat_state_populator.vala:145 #, c-format msgid "%s, %s and %i others" msgstr "%s ใ•ใ‚“ใ€%s ใ•ใ‚“ใจใใฎไป– %i ไบบ" #: main/src/ui/conversation_summary/chat_state_populator.vala:147 #, c-format msgid "%s, %s and %s" msgstr "%s ใ•ใ‚“ใ€%s ใ•ใ‚“ใจ %s ใ•ใ‚“" #: main/src/ui/conversation_summary/chat_state_populator.vala:149 #, c-format msgid "%s and %s" msgstr "%s ใ•ใ‚“ใจ %s ใ•ใ‚“" #: main/src/ui/conversation_summary/chat_state_populator.vala:154 msgid "is typingโ€ฆ" msgid_plural "are typingโ€ฆ" msgstr[0] "ใŒๅ…ฅๅŠ›ใ—ใฆใ„ใพใ™โ€ฆ" #: main/src/ui/conversation_summary/chat_state_populator.vala:156 msgid "has stopped typing" msgstr "ใŒๅ…ฅๅŠ›ใ‚’ไธญๆ–ญใ—ใพใ—ใŸ" #: main/src/ui/conversation_summary/content_item_widget_factory.vala:59 msgid "Message too long" msgstr "ใƒกใƒƒใ‚ปใƒผใ‚ธใŒ้•ทใ™ใŽใพใ™" #: main/src/ui/conversation_summary/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "ใ“ใฎ็›ธๆ‰‹ใŒใ‚ใชใŸใ‚’้€ฃ็ตกๅ…ˆใซๅ…ฅใ‚Œใ‚ˆใ†ใจใ—ใฆใ„ใพใ™" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:193 #, no-c-format msgid "%x, %Hโˆถ%M" msgstr "%xใ€%Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:194 #, no-c-format msgid "%x, %lโˆถ%M %p" msgstr "%xใ€%p %lโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:197 #, no-c-format msgid "%b %d, %Hโˆถ%M" msgstr "%b%dๆ—ฅใ€%Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:198 #, no-c-format msgid "%b %d, %lโˆถ%M %p" msgstr "%b%dๆ—ฅใ€%p %lโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:201 #, no-c-format msgid "%a, %Hโˆถ%M" msgstr "%aๆ›œๆ—ฅใ€%Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:202 #, no-c-format msgid "%a, %lโˆถ%M %p" msgstr "%aๆ›œๆ—ฅใ€%p %lโˆถ%M" #: main/src/ui/occupant_menu/list.vala:102 msgid "Owner" msgstr "ใ‚ชใƒผใƒŠใƒผ" #: main/src/ui/occupant_menu/list.vala:104 msgid "Admin" msgstr "็ฎก็†ไบบ" #: main/src/ui/occupant_menu/list.vala:106 msgid "Member" msgstr "ใƒกใƒณใƒใƒผ" #: main/src/ui/occupant_menu/list.vala:108 msgid "User" msgstr "ใƒฆใƒผใ‚ถใƒผ" #: main/src/ui/occupant_menu/view.vala:29 #: main/src/ui/occupant_menu/view.vala:38 msgid "Invite" msgstr "ๆ‹›ๅพ…" #: main/src/ui/occupant_menu/view.vala:37 msgid "Invite to Conference" msgstr "ใƒˆใƒผใ‚ฏใƒซใƒผใƒ ใธๆ‹›ๅพ…" #: main/src/ui/occupant_menu/view.vala:88 msgid "Start private conversation" msgstr "ๅ€‹ไบบใƒใƒฃใƒƒใƒˆใ‚’ๅง‹ใ‚ใ‚‹" #: main/src/ui/occupant_menu/view.vala:96 msgid "Kick" msgstr "้€€ๅ‡บใ•ใ›ใ‚‹" #: main/src/ui/global_search.vala:140 #, c-format msgid "%i search results" msgstr "%i ไปถใฎๆคœ็ดข็ตๆžœ" #: main/src/ui/global_search.vala:167 #, c-format msgid "In %s" msgstr "%s ใงใฎๆคœ็ดข็ตๆžœ" #: main/src/ui/global_search.vala:167 #, fuzzy, c-format msgid "With %s" msgstr "%s ใจ" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "ใƒˆใƒผใ‚ฏใƒซใƒผใƒ ใฎ่ฉณ็ดฐ" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:24 msgid "Contact Details" msgstr "้€ฃ็ตกๅ…ˆใฎ่ฉณ็ดฐ" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "ใƒˆใƒผใ‚ฏใƒซใƒผใƒ ใฎๅๅ‰" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "ใƒˆใƒผใ‚ฏใƒซใƒผใƒ ใฎ่ชฌๆ˜Ž" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "ใƒˆใƒผใ‚ฏใƒซใƒผใƒ ใ‚’่‡ชๅ‹•ๅ‰Š้™คใ—ใชใ„" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "ๆœ€ๅพŒใฎๅ‚ๅŠ ่€…ใŒ้€€ไผšใ—ใฆใ‚‚ใƒˆใƒผใ‚ฏใƒซใƒผใƒ ใ‚’ๆฎ‹ใ—ใพใ™" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "ๅค–้ƒจใ‹ใ‚‰ๆคœ็ดขๅฏ่ƒฝใซใ™ใ‚‹" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "ๅ‚ๅŠ ่€…ใซใ‚ฟใ‚คใƒˆใƒซใฎๅค‰ๆ›ดใ‚’่จฑๅฏใ™ใ‚‹" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Discover real JIDs" msgstr "JID ใฎๆคœ็ดข" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who may discover real JIDs?" msgstr "JID ใฎๆคœ็ดขใ‚’่จฑๅฏใ™ใ‚‹ๅฏพ่ฑก" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 msgid "Password" msgstr "ใƒ‘ใ‚นใƒฏใƒผใƒ‰" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "Password required for room entry, if any" msgstr "ใƒˆใƒผใ‚ฏใƒซใƒผใƒ ใธใฎๅ‚ๅŠ ใซๅฟ…่ฆใชใƒ‘ใ‚นใƒฏใƒผใƒ‰" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "็™บ่จ€ๅˆถ้™" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "็™บ่จ€ๆจฉใฎใ‚ใ‚‹ๅ‚ๅŠ ่€…ใฎใฟใŒใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’้€ใ‚Œใพใ™" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "ใƒกใƒณใƒใƒผๅˆถ" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "ใƒกใƒณใƒใƒผใฎใฟใŒๅ…ฅๅฎคใงใใพใ™" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "ใƒกใƒƒใ‚ปใƒผใ‚ธใฎๅฑฅๆญด" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "ไฟๅญ˜ใ™ใ‚‹ใƒˆใƒผใ‚ฏๅฑฅๆญดใฎๆ•ฐ" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "ใƒˆใƒผใ‚ฏใƒซใƒผใƒ ใฎ่จญๅฎš" #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/src/ui/contact_details/settings_provider.vala:12 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "่จญๅฎš" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "ใƒ–ใƒญใƒƒใ‚ฏ" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "ๅŒๆ–นใฎใ‚ณใƒŸใƒฅใƒ‹ใ‚ฑใƒผใ‚ทใƒงใƒณใจ็Šถๆ…‹ใฎๆ›ดๆ–ฐใŒใƒ–ใƒญใƒƒใ‚ฏใ•ใ‚Œใพใ™" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "ใƒญใƒผใ‚ซใƒซ่จญๅฎš" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "ๅ…ฅๅŠ›ไธญใงใ‚ใ‚‹ใ“ใจใ‚’้€š็Ÿฅ" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "ๆ—ข่ชญ็Šถๆ…‹ใ‚’้€ไฟก" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "้€š็Ÿฅ" #: main/src/ui/contact_details/settings_provider.vala:82 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:124 msgid "On" msgstr "ใ‚ชใƒณ" #: main/src/ui/contact_details/settings_provider.vala:84 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "Off" msgstr "ใ‚ชใƒ•" #: main/src/ui/contact_details/settings_provider.vala:86 msgid "Only when mentioned" msgstr "ใƒกใƒณใ‚ทใƒงใƒณใ•ใ‚ŒใŸๅ ดๅˆใฎใฟ" #: main/src/ui/contact_details/settings_provider.vala:88 #: main/src/ui/contact_details/settings_provider.vala:123 #, c-format msgid "Default: %s" msgstr "ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆ: %s" #: main/src/ui/conversation_titlebar/search_entry.vala:11 msgid "Search messages" msgstr "ใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’ๆคœ็ดข" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "ใƒกใƒณใƒใƒผ" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "ๆ–ฐใ—ใ„ใƒกใƒƒใ‚ปใƒผใ‚ธใŒๅฑŠใ„ใŸใจใใซ้€š็Ÿฅ" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "ใ‚นใƒžใ‚คใƒชใƒผใ‚’็ตตๆ–‡ๅญ—ใซๅค‰ๆ›" #: main/data/manage_accounts/dialog.ui:9 main/data/menu_app.ui:7 msgid "Accounts" msgstr "ใ‚ขใ‚ซใ‚ฆใƒณใƒˆ" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "ใƒญใƒผใ‚ซใƒซใงใฎๅˆฅๅ" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "ใ‚ขใ‚ซใ‚ฆใƒณใƒˆใŒ่จญๅฎšใ•ใ‚Œใฆใ„ใพใ›ใ‚“" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "ใ‚ขใ‚ซใ‚ฆใƒณใƒˆใ‚’่ฟฝๅŠ " #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "ใ‚ตใ‚คใƒณใ‚คใƒณ" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "ใ‚ขใ‚ซใ‚ฆใƒณใƒˆใ‚’ไฝœๆˆ" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "ๅฎ‰ๅ…จใชๆŽฅ็ถšใ‚’็ขบ็ซ‹ใงใใพใ›ใ‚“ใงใ—ใŸ" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "ๆŽฅ็ถš" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "ๅ…ฌ้–‹ใ‚ตใƒผใƒใƒผใ‚’้ธๆŠž" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "ใพใŸใฏใ€ใ‚ตใƒผใƒใƒผใฎใ‚ขใƒ‰ใƒฌใ‚นใ‚’ๆ‰‹ๅ…ฅๅŠ›" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "ไปฃใ‚ใ‚Šใซใ‚ตใ‚คใƒณใ‚คใƒณ" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "ๅˆฅใฎใ‚ตใƒผใƒใƒผใ‚’้ธๆŠž" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "ใ™ในใฆใฎใ‚ปใƒƒใƒˆใ‚ขใƒƒใƒ—ใŒๅฎŒไบ†ใ—ใพใ—ใŸ๏ผ" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "ๅฎŒไบ†" #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "้žๆš—ๅทๅŒ–" #: main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "ใ‚ญใƒผใƒœใƒผใƒ‰ใ‚ทใƒงใƒผใƒˆใ‚ซใƒƒใƒˆ" #: main/data/menu_app.ui:21 msgid "About Dino" msgstr "Dino ใซใคใ„ใฆ" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "็พไปฃ็š„ใช XMPP ใƒใƒฃใƒƒใƒˆ ใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆ" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino ใฏใ‚ชใƒผใƒ—ใƒณใ‚ฝใƒผใ‚นใฎ็พไปฃ็š„ใชใƒ‡ใ‚นใ‚ฏใƒˆใƒƒใƒ—ๅ‘ใ‘ใƒใƒฃใƒƒใƒˆใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆใงใ™ใ€‚ใƒ—ใƒฉ" "ใ‚คใƒใ‚ทใƒผใ‚’่€ƒๆ…ฎใ—ใคใคใ€ใ‚ทใƒณใƒ—ใƒซใงไฟก้ ผใงใใ‚‹ Jabber/XMPP ใ‚จใ‚ฏใ‚นใƒšใƒชใ‚จใƒณใ‚นใฎๆไพ›" "ใ‚’็ฌฌไธ€ใซ่€ƒใˆใฆ้–‹็™บใ•ใ‚Œใฆใ„ใพใ™ใ€‚" #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "OMEMO ใจ OpenPGP ใ‚’ๅˆฉ็”จใ—ใŸใ‚จใƒณใƒ‰ใƒ„ใƒผใ‚จใƒณใƒ‰ๆš—ๅทๅŒ–ใซๅฏพๅฟœใ—ใฆใŠใ‚Šใ€ๆ—ข่ชญ็Šถๆ…‹ใฎ้€" "ไฟกใ‚„ๅ…ฅๅŠ›้€š็Ÿฅใชใฉใฎใƒ—ใƒฉใ‚คใƒใ‚ทใƒผ้–ข้€ฃใฎ่จญๅฎšใ‚‚ๅฏ่ƒฝใงใ™ใ€‚" #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino ใฏใ‚ตใƒผใƒใƒผใ‹ใ‚‰ๅฑฅๆญดใ‚’ๅ–ๅพ—ใ—ใ€ใปใ‹ใฎใƒ‡ใƒใ‚คใ‚นใจใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’ๅŒๆœŸใ—ใพใ™ใ€‚" #: main/data/im.dino.Dino.appdata.xml.in:16 msgid "Main window with conversations" msgstr "ใƒˆใƒผใ‚ฏไธญใฎใƒกใ‚คใƒณใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆ" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "ใ‚ขใ‚ซใ‚ฆใƒณใƒˆ" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "ใƒ‹ใƒƒใ‚ฏใƒใƒผใƒ " #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "ๅˆฅๅ" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "้€ฃ็ตกๅ…ˆใ‚’่ฟฝๅŠ " #: main/data/shortcuts.ui:12 msgid "General" msgstr "ไธ€่ˆฌ" #: main/data/shortcuts.ui:32 msgid "Navigation" msgstr "ใƒŠใƒ“ใ‚ฒใƒผใ‚ทใƒงใƒณ" #: main/data/shortcuts.ui:37 msgid "Jump to next conversation" msgstr "ๆฌกใฎใƒˆใƒผใ‚ฏใธ็งปๅ‹•" #: main/data/shortcuts.ui:44 msgid "Jump to previous conversation" msgstr "ๅ‰ใฎใƒˆใƒผใ‚ฏใธ็งปๅ‹•" #: main/data/global_search.ui:37 msgid "No active search" msgstr "ใพใ ๆคœ็ดขใ—ใฆใ„ใพใ›ใ‚“" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "ๅ…ฅๅŠ›ใ—ใฆๆคœ็ดขใ‚’้–‹ๅง‹ใ—ใฆใใ ใ•ใ„" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "ไธ€่‡ดใ™ใ‚‹ใƒกใƒƒใ‚ปใƒผใ‚ธใฏใ‚ใ‚Šใพใ›ใ‚“" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "ใ‚นใƒšใƒซใ‚’็ขบ่ชใ™ใ‚‹ใ‹ใ€ใƒ•ใ‚ฃใƒซใ‚ฟใƒผใ‚’ๆถˆๅŽปใ—ใฆใฟใฆใใ ใ•ใ„" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "" "ใƒˆใƒผใ‚ฏใ‚’ๅง‹ใ‚ใŸใ‚Šใƒˆใƒผใ‚ฏใƒซใƒผใƒ ใซๅ‚ๅŠ ใ—ใŸใ‚Šใ™ใ‚‹ใซใฏใ€ใ“ใ“ใ‚’ใ‚ฏใƒชใƒƒใ‚ฏใ—ใฆใใ ใ•" "ใ„ใ€‚" #: main/data/unified_main_content.ui:147 msgid "You have no open chats" msgstr "่กจ็คบใ™ใ‚‹ใƒˆใƒผใ‚ฏใฏใพใ ใ‚ใ‚Šใพใ›ใ‚“" #~ msgid "Failed connecting to %s" #~ msgstr "%s ใธใฎๆŽฅ็ถšใซๅคฑๆ•—ใ—ใพใ—ใŸ" #~ msgid "Join Conference" #~ msgstr "ใƒˆใƒผใ‚ฏใƒซใƒผใƒ ใซๅ‚ๅŠ " #~ msgid "File" #~ msgstr "ใƒ•ใ‚กใ‚คใƒซ" #~ msgid "JID should be of the form โ€œuser@example.comโ€" #~ msgstr "JID ใฏ โ€œuser@example.comโ€ ใฎๅฝขๅผใซใ—ใฆใใ ใ•ใ„" #~ msgid "Copy Link Address" #~ msgstr "ใƒชใƒณใ‚ฏใ‚ขใƒ‰ใƒฌใ‚นใ‚’ใ‚ณใƒ”ใƒผ" #~ msgid "Copy" #~ msgstr "ใ‚ณใƒ”ใƒผ" #~ msgid "Select All" #~ msgstr "ใ™ในใฆ้ธๆŠž" #~ msgid "Search" #~ msgstr "ๆคœ็ดข" #~ msgid "Start Chat" #~ msgstr "ไผš่ฉฑใ‚’้–‹ๅง‹" dino-0.1.0/main/po/lb.po0000644000000000000000000006054213614354364013456 0ustar rootrootmsgid "" msgstr "" "Project-Id-Version: Luxembourgish (Dino)\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2019-12-27 15:41+0000\n" "Language-Team: Luxembourgish \n" "Language: lb\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.10\n" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "Image sent" msgstr "Bild geschรฉckt" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "File sent" msgstr "Datei geschรฉckt" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "Image received" msgstr "Bild kritt" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "File received" msgstr "Datei kritt" #: main/src/ui/notifications.vala:98 msgid "Subscription request" msgstr "Abonnement Ufro" #: main/src/ui/notifications.vala:105 main/src/ui/notifications.vala:140 #: main/src/ui/conversation_summary/subscription_notification.vala:37 msgid "Accept" msgstr "Akzeptรฉieren" #: main/src/ui/notifications.vala:106 main/src/ui/notifications.vala:139 #: main/src/ui/conversation_summary/subscription_notification.vala:38 msgid "Deny" msgstr "Verweigeren" #: main/src/ui/notifications.vala:112 #: main/src/ui/manage_accounts/add_account_dialog.vala:258 #: main/src/ui/add_conversation/conference_details_fragment.vala:188 #, c-format msgid "Could not connect to %s" msgstr "Konnt net op %s connectรฉieren" #: main/src/ui/notifications.vala:128 #, c-format msgid "Invitation to %s" msgstr "Aluedung an %s" #: main/src/ui/notifications.vala:129 #, c-format msgid "%s invited you to %s" msgstr "%s huet dech zu %s agelueden" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Konto %s lรคschen?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Lรคschen" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Avatar auswielen" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Auswielen" #: main/src/ui/manage_accounts/dialog.vala:152 main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Ofbriechen" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Biller" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "All Dateien" #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connectingโ€ฆ" msgstr "Verbannenโ€ฆ" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Verbonnen" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Getrennt" #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Falsch Passwuert" #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Invaliden TLS Zertifikat" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Feeler" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Account bรคisetzen" #: main/src/ui/manage_accounts/add_account_dialog.vala:166 #, c-format msgid "Sign in to %s" msgstr "Bei %s umellen" #: main/src/ui/manage_accounts/add_account_dialog.vala:210 #, c-format msgid "You can now start using %s" msgstr "Du kanns elo %s benotzen" #: main/src/ui/manage_accounts/add_account_dialog.vala:263 #: main/src/ui/manage_accounts/add_account_dialog.vala:296 #: main/src/ui/manage_accounts/add_account_dialog.vala:306 #: main/src/ui/manage_accounts/add_account_dialog.vala:382 #: main/src/ui/add_conversation/conference_details_fragment.vala:191 msgid "Invalid address" msgstr "Ongรผlteg Adress" #: main/src/ui/manage_accounts/add_account_dialog.vala:278 msgid "Wrong username or password" msgstr "Falsche Benotzernumm oder Passwuert" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Something went wrong" msgstr "Eppes ass schif gaangen" #: main/src/ui/manage_accounts/add_account_dialog.vala:321 msgid "No response from server" msgstr "Keng ร„ntwertย vum Server" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 #, c-format msgid "Register on %s" msgstr "Ob %s registrรฉieren" #: main/src/ui/manage_accounts/add_account_dialog.vala:330 msgid "The server requires to sign up through a website" msgstr "De Server setzt eng Registrรฉierungย duerch eng Websรคit viraus" #: main/src/ui/manage_accounts/add_account_dialog.vala:332 msgid "Open Registration" msgstr "Oppe Registrรฉierung" #: main/src/ui/manage_accounts/add_account_dialog.vala:344 msgid "Register" msgstr "Registrรฉieren" #: main/src/ui/manage_accounts/add_account_dialog.vala:346 #, c-format msgid "Check %s for information on how to sign up" msgstr "Kuck %s fir Informatioune iwwert Registrรฉierung" #: main/src/ui/conversation_selector/conversation_selector_row.vala:146 #: main/src/ui/conversation_selector/conversation_selector_row.vala:160 #: main/src/ui/util/helper.vala:136 main/src/ui/util/helper.vala:151 msgid "Me" msgstr "Ech" #: main/src/ui/conversation_selector/conversation_selector_row.vala:258 #: main/src/ui/conversation_summary/date_separator_populator.vala:100 #, no-c-format msgid "%b %d" msgstr "%b %d" #: main/src/ui/conversation_selector/conversation_selector_row.vala:262 #: main/src/ui/conversation_summary/date_separator_populator.vala:91 msgid "Yesterday" msgstr "Gรซschter" #: main/src/ui/conversation_selector/conversation_selector_row.vala:265 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:205 #, no-c-format msgid "%Hโˆถ%M" msgstr "%H:%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:266 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:206 #, no-c-format msgid "%lโˆถ%M %p" msgstr "%l:%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:269 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:210 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i Minutt hier" msgstr[1] "%i Minutten hier" #: main/src/ui/conversation_selector/conversation_selector_row.vala:271 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:212 msgid "Just now" msgstr "Just elo" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/unified_window.vala:249 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Kanal bรคitrieden" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 msgid "Join" msgstr "Bรคitrieden" #: main/src/ui/util/helper.vala:126 #, c-format msgid "%s from %s" msgstr "%s vum %s" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:28 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Bรคisetzen" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:42 msgid "Save" msgstr "Spรคicheren" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Nรคchst" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Zerรฉck" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/src/ui/unified_window.vala:248 #: main/data/conversation_list_titlebar_csd.ui:12 #: main/data/conversation_list_titlebar.ui:16 main/data/menu_add.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:20 main/data/shortcuts.ui:17 msgid "Start Conversation" msgstr "Konversatioun starten" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Start" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joiningโ€ฆ" msgstr "Bรคitriedenโ€ฆ" #: main/src/ui/add_conversation/conference_details_fragment.vala:169 msgid "Password required to enter room" msgstr "Passwuert gรซttย gebraucht fir deย Raum ze betrieden" #: main/src/ui/add_conversation/conference_details_fragment.vala:174 msgid "Banned from joining or creating conference" msgstr "Gebannt vum bรคitrieden an erstelle vuย Konferenzen" #: main/src/ui/add_conversation/conference_details_fragment.vala:176 msgid "Room does not exist" msgstr "Raum existรฉiert net" #: main/src/ui/add_conversation/conference_details_fragment.vala:178 msgid "Not allowed to create room" msgstr "Net erlaabt eeย Raum ze erstellen" #: main/src/ui/add_conversation/conference_details_fragment.vala:180 msgid "Members-only room" msgstr "Raum ass nรซmmeย fir Memberen" #: main/src/ui/add_conversation/conference_details_fragment.vala:183 msgid "Choose a different nick" msgstr "Wiel een anereย Spรซtznumm" #: main/src/ui/add_conversation/conference_details_fragment.vala:185 msgid "Too many occupants in room" msgstr "De Raum huet ze villย Benotzer" #: main/src/ui/unified_window.vala:227 msgid "Welcome to Dino!" msgstr "Wรซllkomm bei Dino!" #: main/src/ui/unified_window.vala:228 msgid "Sign in or create an account to get started." msgstr "Logg dech an oder kreรฉier een Account fir lass ze leen." #: main/src/ui/unified_window.vala:229 msgid "Set up account" msgstr "Konto opsetzen" #: main/src/ui/unified_window.vala:237 msgid "No active accounts" msgstr "Keng Kontoen aktiv" #: main/src/ui/unified_window.vala:238 msgid "Manage accounts" msgstr "Kontoe managen" #: main/src/ui/unified_window.vala:247 msgid "No active conversations" msgstr "Keng Conversatiounenย aktiv" #: main/src/ui/conversation_summary/file_widget.vala:275 #, c-format msgid "Downloading %sโ€ฆ" msgstr "Lueden %s erofโ€ฆ" #: main/src/ui/conversation_summary/file_widget.vala:281 #, c-format msgid "%s offered: %s" msgstr "%s offrรฉiert: %s" #: main/src/ui/conversation_summary/file_widget.vala:283 #, c-format msgid "File offered: %s" msgstr "Datei offrรฉiert: %s" #: main/src/ui/conversation_summary/file_widget.vala:285 msgid "File offered" msgstr "Datei offrรฉiert" #: main/src/ui/conversation_summary/file_widget.vala:290 msgid "File transfer failed" msgstr "Dateitransfert ass feelgeschloen" #: main/src/ui/conversation_summary/date_separator_populator.vala:85 msgid "Today" msgstr "Haut" #: main/src/ui/conversation_summary/date_separator_populator.vala:98 msgid "%a, %b %d" msgstr "%a, %b %d" #: main/src/ui/conversation_summary/chat_state_populator.vala:145 #, c-format msgid "%s, %s and %i others" msgstr "%s, %s an %i anerer" #: main/src/ui/conversation_summary/chat_state_populator.vala:147 #, c-format msgid "%s, %s and %s" msgstr "%s, %s an %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:149 #, c-format msgid "%s and %s" msgstr "%s an %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:154 msgid "is typingโ€ฆ" msgid_plural "are typingโ€ฆ" msgstr[0] "tippt gradโ€ฆ" msgstr[1] "tippe gradโ€ฆ" #: main/src/ui/conversation_summary/chat_state_populator.vala:156 msgid "has stopped typing" msgstr "huet opgehalen ze tippen" #: main/src/ui/conversation_summary/content_item_widget_factory.vala:59 msgid "Message too long" msgstr "Noriicht ze laang" #: main/src/ui/conversation_summary/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Dรซse Kontakt wรฉilt dech gรคrenย an seng Kontaktlecht ophuelen" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:193 #, no-c-format msgid "%x, %Hโˆถ%M" msgstr "%x, %H:%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:194 #, no-c-format msgid "%x, %lโˆถ%M %p" msgstr "%x, %l:%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:197 #, no-c-format msgid "%b %d, %Hโˆถ%M" msgstr "%b %d, %H:%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:198 #, no-c-format msgid "%b %d, %lโˆถ%M %p" msgstr "%b %d, %l:%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:201 #, no-c-format msgid "%a, %Hโˆถ%M" msgstr "%a, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:202 #, no-c-format msgid "%a, %lโˆถ%M %p" msgstr "%a, %lโˆถ%M %p" #: main/src/ui/occupant_menu/list.vala:102 msgid "Owner" msgstr "Besรซtzer" #: main/src/ui/occupant_menu/list.vala:104 msgid "Admin" msgstr "Administrateur" #: main/src/ui/occupant_menu/list.vala:106 msgid "Member" msgstr "Member" #: main/src/ui/occupant_menu/list.vala:108 msgid "User" msgstr "Benotzer" #: main/src/ui/occupant_menu/view.vala:29 #: main/src/ui/occupant_menu/view.vala:38 msgid "Invite" msgstr "Alueden" #: main/src/ui/occupant_menu/view.vala:37 msgid "Invite to Conference" msgstr "Invitรฉieren an Konferenz" #: main/src/ui/occupant_menu/view.vala:88 msgid "Start private conversation" msgstr "Privat Conversatiounย starten" #: main/src/ui/occupant_menu/view.vala:96 msgid "Kick" msgstr "Erausgeheien" #: main/src/ui/global_search.vala:140 #, c-format msgid "%i search results" msgstr "%i Sichresultater" #: main/src/ui/global_search.vala:167 #, c-format msgid "In %s" msgstr "An %s" #: main/src/ui/global_search.vala:167 #, c-format msgid "With %s" msgstr "Mat %s" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Konferenz Detailer" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:24 msgid "Contact Details" msgstr "Kontaktdetailer" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Numm vum Raum" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Beschreiwung vum Raum" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Dauerhaft" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "De Raum bleiftย bestoen, nodeems de leschteย Benotzer erausgaangenย ass" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "ร‹ffentlech siichtbar" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Benotzer dรคerfeย Sujet รคnneren" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Discover real JIDs" msgstr "Echt JIDs gesinn" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who may discover real JIDs?" msgstr "Ween dรคerfย echt JIDs gesinn?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 msgid "Password" msgstr "Passwuert" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "Password required for room entry, if any" msgstr "Passwuert fir deย Raum ze betrieden, wann een gebraucht gรซtt" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderรฉiert" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Nรซmme Benotzer mat Stemm dรคerfe Messagรซย schรฉcken" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Nรซmmeย fir Memberen" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Nรซmme Memberen dรคerfen deย Raum betrieden" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Messagen Historique" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Maximal Unzuel vum aleย Messagen, dรฉi vum Raum geschรฉcktย ginn" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Raum Astellungen" #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/src/ui/contact_details/settings_provider.vala:12 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Astellungen" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Blockรฉieren" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "Kommunikatioun aย Status Updates sinn an all Richtung blockรฉiert" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Lokal Astellungen" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Tipp-Notifikatiouneย schรฉcken" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Scheck Lies Bestรคtegungen" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Notifikatiounen" #: main/src/ui/contact_details/settings_provider.vala:82 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:124 msgid "On" msgstr "Un" #: main/src/ui/contact_details/settings_provider.vala:84 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "Off" msgstr "Aus" #: main/src/ui/contact_details/settings_provider.vala:86 msgid "Only when mentioned" msgstr "Nรซmme wann erwรครคnt" #: main/src/ui/contact_details/settings_provider.vala:88 #: main/src/ui/contact_details/settings_provider.vala:123 #, c-format msgid "Default: %s" msgstr "Standard: %s" #: main/src/ui/conversation_titlebar/search_entry.vala:11 msgid "Search messages" msgstr "Messagen duerchsichen" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Memberen" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Benoriichtege wann een neieย Message ukรซnnt" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Smileyen an Emojienย konvertรฉieren" #: main/data/manage_accounts/dialog.ui:9 main/data/menu_app.ui:7 msgid "Accounts" msgstr "Kontoen" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Lokalen Pseudonym" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Keng Kontoeย konfigurรฉiert" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Ee Konto bรคisetzen" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Aloggen" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Account kreรฉieren" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Konnt keng gesรฉchertย Verbindung opbauen" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Connectรฉieren" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "ร‹ffentleche Server auswielen" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Oder spezifizรฉierย eng Server Adress" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Aloggen a Platz" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Anere Server auswielen" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Alles ageriicht!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Ofschlรฉissen" #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Onverschlรซsselt" #: main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Tastaturofkierzungen" #: main/data/menu_app.ui:21 msgid "About Dino" msgstr "Iwwert Dino" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Modernen XMPP Chat Client" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino assย e modernen, quell-offeneย Chat Client fir den Desktop. Hien biet eng " "opgeraumtย a robust Jabber/XMPP Erfarungย a leet ee Schwรฉierpunkt op " "Privatsphรคr." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Hien รซnnerstรซtzย Enn-zu-Enn Verschlรซsselungย mat OMEMO an OpenPGP an enthรคlt " "Privatsphรคre-Astellungen zu Liesbestรคtegungenย an Tipp-Benoriichtegungen." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino rifftย  Gesprรฉichverlรคf vum Server of a synchronisรฉiert Noriichteย mat " "anereย Gerรคter." #: main/data/im.dino.Dino.appdata.xml.in:16 msgid "Main window with conversations" msgstr "Haaptfรซnsterย mat den Conversatiounen" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Konto" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Spรซtznumm" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Pseudonym" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Kontakt bรคisetzen" #: main/data/shortcuts.ui:12 msgid "General" msgstr "Allgemeng" #: main/data/shortcuts.ui:32 msgid "Navigation" msgstr "Navigatioun" #: main/data/shortcuts.ui:37 msgid "Jump to next conversation" msgstr "Zu nรคchster Konversatioun sprangen" #: main/data/shortcuts.ui:44 msgid "Jump to previous conversation" msgstr "Zu viregter Konversatioun sprangen" #: main/data/global_search.ui:37 msgid "No active search" msgstr "Kengย aktiv Sich" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Tipp fir eng Sich ze starten" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Keng Messageย fonnt" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Iwwerprรฉif Schreifweisย oder lรคschย Filteren" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "" "Klick hei fir eng Konversatioun ze starten oder engem Channel bรคizetrieden." #: main/data/unified_main_content.ui:147 msgid "You have no open chats" msgstr "Du hues keng oppen Chats" #~ msgid "Failed connecting to %s" #~ msgstr "Feeler beim connectรฉieren op %s" #~ msgid "Join Conference" #~ msgstr "Konferenz bรคitrieden" #~ msgid "File" #~ msgstr "Datei" #~ msgid "Communicate happiness." #~ msgstr "Glรฉck kommunizรฉieren." #~ msgid "Preferences" #~ msgstr "Astellungen" #~ msgid "Quit" #~ msgstr "Zoumaachen" #~ msgid "JID should be of the form โ€œuser@example.comโ€" #~ msgstr "JID sollt folgend Form hunn โ€žuser@example.comโ€œ" #~ msgid "Copy Link Address" #~ msgstr "Kopรฉier Link Adress" #~ msgid "Copy" #~ msgstr "Kopรฉieren" #~ msgid "Select All" #~ msgstr "Alles auswielen" #~ msgid "Search" #~ msgstr "Sichen" #~ msgid "Send message marker" #~ msgstr "Message Marker verschรฉcken" #~ msgid "Start Chat" #~ msgstr "Chat starten" #~ msgid "Request presence updates" #~ msgstr "Presenz Updatenย ufroen" #~ msgid "This is an app-notification. Click the button to dismiss" #~ msgstr "" #~ "Dat heiten ass eng App-Notifikatioun. Klick de Knรคppcheย fir ze ewech ze " #~ "maachen" dino-0.1.0/main/po/nb.po0000644000000000000000000006026713614354364013464 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2019-12-26 23:21+0000\n" "Language-Team: Norwegian Bokmรฅl \n" "Language: nb\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.10\n" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "Image sent" msgstr "Bilde sendt" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "File sent" msgstr "Fil sendt" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "Image received" msgstr "Bilde mottatt" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "File received" msgstr "Fil mottatt" #: main/src/ui/notifications.vala:98 msgid "Subscription request" msgstr "Abonnementsforespรธrsel" #: main/src/ui/notifications.vala:105 main/src/ui/notifications.vala:140 #: main/src/ui/conversation_summary/subscription_notification.vala:37 msgid "Accept" msgstr "Godta" #: main/src/ui/notifications.vala:106 main/src/ui/notifications.vala:139 #: main/src/ui/conversation_summary/subscription_notification.vala:38 msgid "Deny" msgstr "Nekt" #: main/src/ui/notifications.vala:112 #: main/src/ui/manage_accounts/add_account_dialog.vala:258 #: main/src/ui/add_conversation/conference_details_fragment.vala:188 #, c-format msgid "Could not connect to %s" msgstr "Kunne ikke koble til %s" #: main/src/ui/notifications.vala:128 #, c-format msgid "Invitation to %s" msgstr "Invitasjon til %s" #: main/src/ui/notifications.vala:129 #, c-format msgid "%s invited you to %s" msgstr "%s inviterte deg til %s" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Fjern konto %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Fjern" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Velg avatar" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Velg" #: main/src/ui/manage_accounts/dialog.vala:152 main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Avbryt" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Bilder" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Alle filer" #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connectingโ€ฆ" msgstr "Kobler tilโ€ฆ" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Tilkoblet" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Frakoblet" #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Feil passord" #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Ugyldig TLS-sertifikat" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Feil" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Legg til konto" #: main/src/ui/manage_accounts/add_account_dialog.vala:166 #, c-format msgid "Sign in to %s" msgstr "Logg inn pรฅ %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:210 #, c-format msgid "You can now start using %s" msgstr "Du kan nรฅ begynne รฅ bruke %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:263 #: main/src/ui/manage_accounts/add_account_dialog.vala:296 #: main/src/ui/manage_accounts/add_account_dialog.vala:306 #: main/src/ui/manage_accounts/add_account_dialog.vala:382 #: main/src/ui/add_conversation/conference_details_fragment.vala:191 msgid "Invalid address" msgstr "Ugyldig adresse" #: main/src/ui/manage_accounts/add_account_dialog.vala:278 msgid "Wrong username or password" msgstr "Feil brukernavn eller passord" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Something went wrong" msgstr "Noe gikk galt" #: main/src/ui/manage_accounts/add_account_dialog.vala:321 msgid "No response from server" msgstr "Inget svar fra tjener" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 #, c-format msgid "Register on %s" msgstr "Registrer pรฅ %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:330 msgid "The server requires to sign up through a website" msgstr "Tjeneren krever at man logger inn via en nettside" #: main/src/ui/manage_accounts/add_account_dialog.vala:332 msgid "Open Registration" msgstr "ร…pne registrering" #: main/src/ui/manage_accounts/add_account_dialog.vala:344 msgid "Register" msgstr "Registrer" #: main/src/ui/manage_accounts/add_account_dialog.vala:346 #, c-format msgid "Check %s for information on how to sign up" msgstr "Se %s for รฅ lรฆre hvordan du registrerer deg" #: main/src/ui/conversation_selector/conversation_selector_row.vala:146 #: main/src/ui/conversation_selector/conversation_selector_row.vala:160 #: main/src/ui/util/helper.vala:136 main/src/ui/util/helper.vala:151 msgid "Me" msgstr "Meg" #: main/src/ui/conversation_selector/conversation_selector_row.vala:258 #: main/src/ui/conversation_summary/date_separator_populator.vala:100 #, no-c-format msgid "%b %d" msgstr "%b %d" #: main/src/ui/conversation_selector/conversation_selector_row.vala:262 #: main/src/ui/conversation_summary/date_separator_populator.vala:91 msgid "Yesterday" msgstr "I gรฅr" #: main/src/ui/conversation_selector/conversation_selector_row.vala:265 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:205 #, no-c-format msgid "%Hโˆถ%M" msgstr "%Hโˆถ%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:266 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:206 #, no-c-format msgid "%lโˆถ%M %p" msgstr "%lโˆถ%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:269 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:210 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i minutt siden" msgstr[1] "%i minutter siden" #: main/src/ui/conversation_selector/conversation_selector_row.vala:271 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:212 msgid "Just now" msgstr "Akkurat nรฅ" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/unified_window.vala:249 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Ta del i kanal" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 msgid "Join" msgstr "Ta del" #: main/src/ui/util/helper.vala:126 #, c-format msgid "%s from %s" msgstr "%s fra %s" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:28 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Legg til" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:42 msgid "Save" msgstr "Lagre" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Neste" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Tilbake" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/src/ui/unified_window.vala:248 #: main/data/conversation_list_titlebar_csd.ui:12 #: main/data/conversation_list_titlebar.ui:16 main/data/menu_add.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:20 main/data/shortcuts.ui:17 msgid "Start Conversation" msgstr "Start samtale" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Start" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joiningโ€ฆ" msgstr "Tar delโ€ฆ" #: main/src/ui/add_conversation/conference_details_fragment.vala:169 msgid "Password required to enter room" msgstr "Passord kreves for รฅ ta del i rommet" #: main/src/ui/add_conversation/conference_details_fragment.vala:174 msgid "Banned from joining or creating conference" msgstr "Bannlyst fra รฅ ta del i eller รฅ opprette konferanser" #: main/src/ui/add_conversation/conference_details_fragment.vala:176 msgid "Room does not exist" msgstr "Rommet finnes ikke" #: main/src/ui/add_conversation/conference_details_fragment.vala:178 msgid "Not allowed to create room" msgstr "Ikke tillatt รฅ opprette rom" #: main/src/ui/add_conversation/conference_details_fragment.vala:180 msgid "Members-only room" msgstr "Rommet er kun for medlemmer" #: main/src/ui/add_conversation/conference_details_fragment.vala:183 msgid "Choose a different nick" msgstr "Velg et annet kallenavn" #: main/src/ui/add_conversation/conference_details_fragment.vala:185 msgid "Too many occupants in room" msgstr "Rommet har for mange deltagere" #: main/src/ui/unified_window.vala:227 msgid "Welcome to Dino!" msgstr "Velkommen til Dino." #: main/src/ui/unified_window.vala:228 msgid "Sign in or create an account to get started." msgstr "Logg inn eller opprett en konto for รฅ starte." #: main/src/ui/unified_window.vala:229 msgid "Set up account" msgstr "Sett opp konto" #: main/src/ui/unified_window.vala:237 msgid "No active accounts" msgstr "Ingen kontoer aktive" #: main/src/ui/unified_window.vala:238 msgid "Manage accounts" msgstr "Behandle kontoer" #: main/src/ui/unified_window.vala:247 msgid "No active conversations" msgstr "Ingen kontoer aktive" #: main/src/ui/conversation_summary/file_widget.vala:275 #, c-format msgid "Downloading %sโ€ฆ" msgstr "Laster ned %sโ€ฆ" #: main/src/ui/conversation_summary/file_widget.vala:281 #, c-format msgid "%s offered: %s" msgstr "%s tilbรธd: %s" #: main/src/ui/conversation_summary/file_widget.vala:283 #, c-format msgid "File offered: %s" msgstr "Fil tilbudt: %s" #: main/src/ui/conversation_summary/file_widget.vala:285 msgid "File offered" msgstr "Fil tilbudt" #: main/src/ui/conversation_summary/file_widget.vala:290 msgid "File transfer failed" msgstr "Filoverfรธring mislyktes" #: main/src/ui/conversation_summary/date_separator_populator.vala:85 msgid "Today" msgstr "I dag" #: main/src/ui/conversation_summary/date_separator_populator.vala:98 msgid "%a, %b %d" msgstr "%a, %d %b" #: main/src/ui/conversation_summary/chat_state_populator.vala:145 #, c-format msgid "%s, %s and %i others" msgstr "%s, %s og %i andre" #: main/src/ui/conversation_summary/chat_state_populator.vala:147 #, c-format msgid "%s, %s and %s" msgstr "%s, %s og %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:149 #, c-format msgid "%s and %s" msgstr "%s og %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:154 msgid "is typingโ€ฆ" msgid_plural "are typingโ€ฆ" msgstr[0] "skriverโ€ฆ" msgstr[1] "skriverโ€ฆ" #: main/src/ui/conversation_summary/chat_state_populator.vala:156 msgid "has stopped typing" msgstr "har tatt pause i skrivinga" #: main/src/ui/conversation_summary/content_item_widget_factory.vala:59 msgid "Message too long" msgstr "Meldingen er for lang" #: main/src/ui/conversation_summary/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Denne kontakten รธnsker รฅ legge deg til pรฅ kontaktlisten sin" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:193 #, no-c-format msgid "%x, %Hโˆถ%M" msgstr "%x, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:194 #, no-c-format msgid "%x, %lโˆถ%M %p" msgstr "%x, %lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:197 #, no-c-format msgid "%b %d, %Hโˆถ%M" msgstr "%b %d, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:198 #, no-c-format msgid "%b %d, %lโˆถ%M %p" msgstr "%b %d, %lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:201 #, no-c-format msgid "%a, %Hโˆถ%M" msgstr "%a, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:202 #, no-c-format msgid "%a, %lโˆถ%M %p" msgstr "%a, %lโˆถ%M %p" #: main/src/ui/occupant_menu/list.vala:102 msgid "Owner" msgstr "Eier" #: main/src/ui/occupant_menu/list.vala:104 msgid "Admin" msgstr "Admin" #: main/src/ui/occupant_menu/list.vala:106 msgid "Member" msgstr "Medlem" #: main/src/ui/occupant_menu/list.vala:108 msgid "User" msgstr "Bruker" #: main/src/ui/occupant_menu/view.vala:29 #: main/src/ui/occupant_menu/view.vala:38 msgid "Invite" msgstr "Inviter" #: main/src/ui/occupant_menu/view.vala:37 msgid "Invite to Conference" msgstr "Inviter til konferanse" #: main/src/ui/occupant_menu/view.vala:88 msgid "Start private conversation" msgstr "Start privat samtale" #: main/src/ui/occupant_menu/view.vala:96 msgid "Kick" msgstr "Kast ut" #: main/src/ui/global_search.vala:140 #, c-format msgid "%i search results" msgstr "%i sรธkeresultater" #: main/src/ui/global_search.vala:167 #, c-format msgid "In %s" msgstr "I %s" #: main/src/ui/global_search.vala:167 #, c-format msgid "With %s" msgstr "Med %s" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Konferansedetaljer" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:24 msgid "Contact Details" msgstr "Kontaktdetaljer" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Romnavn" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Rombeskrivelse" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Vedvarende" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "Rommet vil vedvare etter at siste deltager forlater" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Offentlig sรธkbart" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Deltagere kan endre emne" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Discover real JIDs" msgstr "Oppdag ekte JID-er" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who may discover real JIDs?" msgstr "Hvem har tilgang til oppdagelse av ekte JID-er?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 msgid "Password" msgstr "Passord" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "Password required for room entry, if any" msgstr "Passord som kreves for รฅ ta del i rommet. La stรฅ tomt for inget" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderert" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Bare deltagere med stemme kan sende meldinger" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Kun for medlemmer" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Kun medlemmer har adgang til rommet" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Meldingshistorikk" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Maksimalt antall meldinger med historikk som skal svares av rommet" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Rom-oppsett" #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/src/ui/contact_details/settings_provider.vala:12 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Innstillinger" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Blokker" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "Kommunikasjon og statusoppdateringer i begge retninger er blokkert" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Lokale innstillinger" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Send skrivevarsling" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Send meldingskvitteringer" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Merknader" #: main/src/ui/contact_details/settings_provider.vala:82 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:124 msgid "On" msgstr "Pรฅ" #: main/src/ui/contact_details/settings_provider.vala:84 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "Off" msgstr "Av" #: main/src/ui/contact_details/settings_provider.vala:86 msgid "Only when mentioned" msgstr "Bare nรฅr nevnt" #: main/src/ui/contact_details/settings_provider.vala:88 #: main/src/ui/contact_details/settings_provider.vala:123 #, c-format msgid "Default: %s" msgstr "Forvalg: %s" #: main/src/ui/conversation_titlebar/search_entry.vala:11 msgid "Search messages" msgstr "Sรธk i meldinger" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Medlemmer" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Varsle ved mottak av ny melding" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Konverter smilefjes til emoji-er" #: main/data/manage_accounts/dialog.ui:9 main/data/menu_app.ui:7 msgid "Accounts" msgstr "Kontoer" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Lokalt alias" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Ingen kontoer satt opp" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Legg til en konto" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Logg inn" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Opprett konto" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Kunne ikke etablere sikker forbindelse" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Koble til" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Velg en offentlig tjener" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Eller angi en tjeneradresse" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Logg inn istedenfor" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Velg en annen tjener" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Ferdig oppsatt." #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Fullfรธr" #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Ukryptert" #: main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Tastatursnarveier" #: main/data/menu_app.ui:21 msgid "About Dino" msgstr "Om Dino" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Moderne XMPP-sludreklient" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino er en moderne friporg-sludringsklient for skrivebordet. Det fokuserer " "pรฅ rask og pรฅlitelig XMPP-opplevelse, samtidig som det hegner om " "personvernet." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Det stรธtter ende-til-ende -kryptering med OMEMO og OpenPGP, og tillater " "oppsett av personvernsrelaterte funksjoner som meldingskvitteringer og " "skrivevarsling." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino henter historikk fra tjeneren og synkroniserer meldinger med andre " "enheter." #: main/data/im.dino.Dino.appdata.xml.in:16 msgid "Main window with conversations" msgstr "Hovedvindu med samtaler" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Konto" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Kallenavn" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Alias" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Legg til kontakt" #: main/data/shortcuts.ui:12 msgid "General" msgstr "Generelt" #: main/data/shortcuts.ui:32 msgid "Navigation" msgstr "Navigasjon" #: main/data/shortcuts.ui:37 msgid "Jump to next conversation" msgstr "Hopp til neste samtale" #: main/data/shortcuts.ui:44 msgid "Jump to previous conversation" msgstr "Hopp til forrige samtale" #: main/data/global_search.ui:37 msgid "No active search" msgstr "Inget aktivt sรธk" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Skriv for รฅ starte et sรธk" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Ingen samsvarende meldinger" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Sjekk stavingen eller prรธv รฅ fjerne filter" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "Klikk her for รฅ starte en samtale, eller ta del i en kanal." #: main/data/unified_main_content.ui:147 msgid "You have no open chats" msgstr "Du har ingen รฅpne sludringer" #~ msgid "Failed connecting to %s" #~ msgstr "Klarte ikke รฅ koble til %s" #~ msgid "Join Conference" #~ msgstr "Ta del i konferanse" #~ msgid "File" #~ msgstr "Fil" #~ msgid "Communicate happiness." #~ msgstr "Kommuniser glede." #~ msgid "Preferences" #~ msgstr "Innstillinger" #~ msgid "Quit" #~ msgstr "Avslutt" #~ msgid "JID should be of the form โ€œuser@example.comโ€" #~ msgstr "JID skal vรฆre av typen \"bruker@eksmepel.no\"" #~ msgid "Copy Link Address" #~ msgstr "Kopier lenkeadresse" #~ msgid "Copy" #~ msgstr "Kopier" #~ msgid "Select All" #~ msgstr "Velg alle" #~ msgid "Search" #~ msgstr "Sรธk" #~ msgid "Send message marker" #~ msgstr "Send meldingsmarkรธr" #~ msgid "Start Chat" #~ msgstr "Start sludring" #~ msgid "Request presence updates" #~ msgstr "Forespรธr tilstedevรฆrelsesoppdateringer" #~ msgid "This is an app-notification. Click the button to dismiss" #~ msgstr "Dette er en merknad i programmet. Klikk pรฅ knappen for รฅ avvise" #~ msgid "Join on startup" #~ msgstr "Ta del ved oppstart" #~ msgid "Add Chat" #~ msgstr "Legg til sludring" dino-0.1.0/main/po/nl.po0000644000000000000000000006104513614354364013471 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2020-01-16 00:21+0000\n" "Language-Team: Dutch \n" "Language: nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.11-dev\n" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "Image sent" msgstr "Afbeelding verzonden" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "File sent" msgstr "Bestand verzonden" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "Image received" msgstr "Afbeelding ontvangen" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "File received" msgstr "Bestand ontvangen" #: main/src/ui/notifications.vala:98 msgid "Subscription request" msgstr "Abonneringsverzoek" #: main/src/ui/notifications.vala:105 main/src/ui/notifications.vala:140 #: main/src/ui/conversation_summary/subscription_notification.vala:37 msgid "Accept" msgstr "Toestaan" #: main/src/ui/notifications.vala:106 main/src/ui/notifications.vala:139 #: main/src/ui/conversation_summary/subscription_notification.vala:38 msgid "Deny" msgstr "Afwijzen" #: main/src/ui/notifications.vala:112 #: main/src/ui/manage_accounts/add_account_dialog.vala:258 #: main/src/ui/add_conversation/conference_details_fragment.vala:188 #, c-format msgid "Could not connect to %s" msgstr "Kon geen verbinding maken met %s" #: main/src/ui/notifications.vala:128 #, c-format msgid "Invitation to %s" msgstr "Uitnodiging voor %s" #: main/src/ui/notifications.vala:129 #, c-format msgid "%s invited you to %s" msgstr "uitnodiging van %s voor %s" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Account %s verwijderen?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Verwijderen" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Kies een avatar" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Selecteren" #: main/src/ui/manage_accounts/dialog.vala:152 main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Annuleren" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Afbeeldingen" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Alle bestanden" #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connectingโ€ฆ" msgstr "Verbinding makenโ€ฆ" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Verbonden" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Verbinding verbroken" #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Verkeerd wachtwoord" #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Ongeldig TLS-certificaat" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Fout" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Account toevoegen" #: main/src/ui/manage_accounts/add_account_dialog.vala:166 #, c-format msgid "Sign in to %s" msgstr "Inloggen bij %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:210 #, c-format msgid "You can now start using %s" msgstr "U kunt %s nu beginnen gebruiken" #: main/src/ui/manage_accounts/add_account_dialog.vala:263 #: main/src/ui/manage_accounts/add_account_dialog.vala:296 #: main/src/ui/manage_accounts/add_account_dialog.vala:306 #: main/src/ui/manage_accounts/add_account_dialog.vala:382 #: main/src/ui/add_conversation/conference_details_fragment.vala:191 msgid "Invalid address" msgstr "Ongeldig adress" #: main/src/ui/manage_accounts/add_account_dialog.vala:278 msgid "Wrong username or password" msgstr "Verkeerde gebruikersnaam of wachtwoord" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Something went wrong" msgstr "Er ging iets mis" #: main/src/ui/manage_accounts/add_account_dialog.vala:321 msgid "No response from server" msgstr "Geen antwoord van server" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 #, c-format msgid "Register on %s" msgstr "Registreren bij %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:330 msgid "The server requires to sign up through a website" msgstr "De server vereist registratie via een website" #: main/src/ui/manage_accounts/add_account_dialog.vala:332 msgid "Open Registration" msgstr "Open registratie" #: main/src/ui/manage_accounts/add_account_dialog.vala:344 msgid "Register" msgstr "Registreren" #: main/src/ui/manage_accounts/add_account_dialog.vala:346 #, c-format msgid "Check %s for information on how to sign up" msgstr "Bekijk %s voor meer informatie over hoe te registreren" #: main/src/ui/conversation_selector/conversation_selector_row.vala:146 #: main/src/ui/conversation_selector/conversation_selector_row.vala:160 #: main/src/ui/util/helper.vala:136 main/src/ui/util/helper.vala:151 msgid "Me" msgstr "Ik" #: main/src/ui/conversation_selector/conversation_selector_row.vala:258 #: main/src/ui/conversation_summary/date_separator_populator.vala:100 #, no-c-format msgid "%b %d" msgstr "%d %b" #: main/src/ui/conversation_selector/conversation_selector_row.vala:262 #: main/src/ui/conversation_summary/date_separator_populator.vala:91 msgid "Yesterday" msgstr "Gisteren" #: main/src/ui/conversation_selector/conversation_selector_row.vala:265 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:205 #, no-c-format msgid "%Hโˆถ%M" msgstr "%Hโˆถ%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:266 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:206 #, no-c-format msgid "%lโˆถ%M %p" msgstr "%lโˆถ%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:269 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:210 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i min geleden" msgstr[1] "%i min geleden" #: main/src/ui/conversation_selector/conversation_selector_row.vala:271 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:212 msgid "Just now" msgstr "Zojuist" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/unified_window.vala:249 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Deelnemen aan kanaal" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 msgid "Join" msgstr "Deelnemen" #: main/src/ui/util/helper.vala:126 #, c-format msgid "%s from %s" msgstr "%s in %s" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:28 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Toevoegen" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:42 msgid "Save" msgstr "Opslaan" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Volgende" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Terug" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/src/ui/unified_window.vala:248 #: main/data/conversation_list_titlebar_csd.ui:12 #: main/data/conversation_list_titlebar.ui:16 main/data/menu_add.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:20 main/data/shortcuts.ui:17 msgid "Start Conversation" msgstr "Gesprek beginnen" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Beginnen" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joiningโ€ฆ" msgstr "Deelnemenโ€ฆ" #: main/src/ui/add_conversation/conference_details_fragment.vala:169 msgid "Password required to enter room" msgstr "Wachtwoord voor groepsgesprek vereist" #: main/src/ui/add_conversation/conference_details_fragment.vala:174 msgid "Banned from joining or creating conference" msgstr "Deelnemen aan of aanmaken van groepsgesprekken verboden" #: main/src/ui/add_conversation/conference_details_fragment.vala:176 msgid "Room does not exist" msgstr "Groepsgesprek bestaat niet" #: main/src/ui/add_conversation/conference_details_fragment.vala:178 msgid "Not allowed to create room" msgstr "Aanmaken van groepsgesprek niet toegestaan" #: main/src/ui/add_conversation/conference_details_fragment.vala:180 msgid "Members-only room" msgstr "Groepsgesprek is alleen-leden" #: main/src/ui/add_conversation/conference_details_fragment.vala:183 msgid "Choose a different nick" msgstr "Kies een andere bijnaam" #: main/src/ui/add_conversation/conference_details_fragment.vala:185 msgid "Too many occupants in room" msgstr "Groepsgesprek heeft te veel deelnemers" #: main/src/ui/unified_window.vala:227 msgid "Welcome to Dino!" msgstr "Welkom bij Dino!" #: main/src/ui/unified_window.vala:228 msgid "Sign in or create an account to get started." msgstr "Log in of maak een account om te beginnen." #: main/src/ui/unified_window.vala:229 msgid "Set up account" msgstr "Account instellen" #: main/src/ui/unified_window.vala:237 msgid "No active accounts" msgstr "Geen accounts actief" #: main/src/ui/unified_window.vala:238 msgid "Manage accounts" msgstr "Accounts beheren" #: main/src/ui/unified_window.vala:247 msgid "No active conversations" msgstr "Geen gesprekken actief" #: main/src/ui/conversation_summary/file_widget.vala:275 #, c-format msgid "Downloading %sโ€ฆ" msgstr "Downloaden %sโ€ฆ" #: main/src/ui/conversation_summary/file_widget.vala:281 #, c-format msgid "%s offered: %s" msgstr "%s bood aan: %s" #: main/src/ui/conversation_summary/file_widget.vala:283 #, c-format msgid "File offered: %s" msgstr "Bestand aangeboden: %s" #: main/src/ui/conversation_summary/file_widget.vala:285 msgid "File offered" msgstr "Bestand aangeboden" #: main/src/ui/conversation_summary/file_widget.vala:290 msgid "File transfer failed" msgstr "Bestandsoverdracht mislukt" #: main/src/ui/conversation_summary/date_separator_populator.vala:85 msgid "Today" msgstr "Vandaag" #: main/src/ui/conversation_summary/date_separator_populator.vala:98 msgid "%a, %b %d" msgstr "%a, %b %d" #: main/src/ui/conversation_summary/chat_state_populator.vala:145 #, c-format msgid "%s, %s and %i others" msgstr "%s, %s en %i anderen" #: main/src/ui/conversation_summary/chat_state_populator.vala:147 #, c-format msgid "%s, %s and %s" msgstr "%s, %s en %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:149 #, c-format msgid "%s and %s" msgstr "%s en %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:154 msgid "is typingโ€ฆ" msgid_plural "are typingโ€ฆ" msgstr[0] "is aan het typenโ€ฆ" msgstr[1] "zijn aan het typenโ€ฆ" #: main/src/ui/conversation_summary/chat_state_populator.vala:156 msgid "has stopped typing" msgstr "is gestopt met typen" #: main/src/ui/conversation_summary/content_item_widget_factory.vala:59 msgid "Message too long" msgstr "Bericht te lang" #: main/src/ui/conversation_summary/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Dit contact wil u toevoegen aan zijn/haar contacten" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:193 #, no-c-format msgid "%x, %Hโˆถ%M" msgstr "%x, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:194 #, no-c-format msgid "%x, %lโˆถ%M %p" msgstr "%x, %lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:197 #, no-c-format msgid "%b %d, %Hโˆถ%M" msgstr "%d %b, %H:%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:198 #, no-c-format msgid "%b %d, %lโˆถ%M %p" msgstr "%d %b, %lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:201 #, no-c-format msgid "%a, %Hโˆถ%M" msgstr "%a, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:202 #, no-c-format msgid "%a, %lโˆถ%M %p" msgstr "%a, %lโˆถ%M %p" #: main/src/ui/occupant_menu/list.vala:102 msgid "Owner" msgstr "Eigenaar" #: main/src/ui/occupant_menu/list.vala:104 msgid "Admin" msgstr "Beheerder" #: main/src/ui/occupant_menu/list.vala:106 msgid "Member" msgstr "Lid" #: main/src/ui/occupant_menu/list.vala:108 msgid "User" msgstr "Gebruiker" #: main/src/ui/occupant_menu/view.vala:29 #: main/src/ui/occupant_menu/view.vala:38 msgid "Invite" msgstr "Uitnodigen" #: main/src/ui/occupant_menu/view.vala:37 msgid "Invite to Conference" msgstr "Uitnodigen in groepsgesprek" #: main/src/ui/occupant_menu/view.vala:88 msgid "Start private conversation" msgstr "Privรฉgesprek beginnen" #: main/src/ui/occupant_menu/view.vala:96 msgid "Kick" msgstr "Schoppen" #: main/src/ui/global_search.vala:140 #, c-format msgid "%i search results" msgstr "%i zoekresultaten" #: main/src/ui/global_search.vala:167 #, c-format msgid "In %s" msgstr "In %s" #: main/src/ui/global_search.vala:167 #, c-format msgid "With %s" msgstr "Met %s" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Groepsgespreksgegevens" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:24 msgid "Contact Details" msgstr "Contactgegevens" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Naam van groepsgesprek" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Beschrijving van groepsgesprek" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Blijvend" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "" "Het groepsgesprek blijft bestaan wanneer de laatste deelnemer het verlaat" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Publiek vindbaar" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Deelnemers kunnen onderwerp aanpassen" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Discover real JIDs" msgstr "Zichtbaarheid van JIDโ€™s" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who may discover real JIDs?" msgstr "Wie mag de JIDโ€™s van de deelnemers zien?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 msgid "Password" msgstr "Wachtwoord" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "Password required for room entry, if any" msgstr "Wachtwoord voor groepsgesprek, indien nodig" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Gemodereerd" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Enkel deelnemers met stem mogen berichten sturen" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Enkel leden" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Enkel leden mogen deelnemen aan het groepsgesprek" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Berichtgeschiedenis" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Maximaal aantal berichten uit geschiedenis dat groepsgesprek weergeeft" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Gespreksconfiguratie" #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/src/ui/contact_details/settings_provider.vala:12 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Instellingen" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Blokkeren" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "Communicatie en statusupdates worden in beide richtingen geblokkeerd" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Lokale instellingen" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Aan-het-typen-meldingen sturen" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Leesbevestigingen sturen" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Meldingen" #: main/src/ui/contact_details/settings_provider.vala:82 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:124 msgid "On" msgstr "Aan" #: main/src/ui/contact_details/settings_provider.vala:84 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "Off" msgstr "Uit" #: main/src/ui/contact_details/settings_provider.vala:86 msgid "Only when mentioned" msgstr "Enkel wanneer vermeld" #: main/src/ui/contact_details/settings_provider.vala:88 #: main/src/ui/contact_details/settings_provider.vala:123 #, c-format msgid "Default: %s" msgstr "Standaard: %s" #: main/src/ui/conversation_titlebar/search_entry.vala:11 msgid "Search messages" msgstr "Zoek berichten" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Leden" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Melding tonen bij nieuw bericht" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Smileys omzetten naar emojiโ€™s" #: main/data/manage_accounts/dialog.ui:9 main/data/menu_app.ui:7 msgid "Accounts" msgstr "Accounts" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Lokale alias" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Geen accounts ingesteld" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Account toevoegen" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Inloggen" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Account aanmaken" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Kon geen beveiligde verbinding opstellen" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Verbinden" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Kies een openbare server" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Of voer een serveradres in" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Met bestaand account inloggen" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Kies een andere server" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Klaar!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Voltooien" #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Onversleuteld" #: main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Sneltoetsen" #: main/data/menu_app.ui:21 msgid "About Dino" msgstr "Over Dino" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Moderne XMPP-chatcliรซnt" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino is een moderne, vrije chattoepassing voor uw bureaublad. Ze biedt een " "eenvoudige en betrouwbare Jabber/XMPP-ervaring, met uw privacy in het " "achterhoofd." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Ze ondersteunt eind-tot-eind-versleuteling met OMEMO en OpenPGP, en staat u " "toe privacy-gerelateerde functies, zoals leesbevestigingen en typmeldingen, " "in te stellen." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino haalt de geschiedenis op van de server en synchroniseert berichten met " "andere apparaten." #: main/data/im.dino.Dino.appdata.xml.in:16 msgid "Main window with conversations" msgstr "Hoofdvenster met gesprekken" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Account" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Schermnaam" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Alias" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Contact toevoegen" #: main/data/shortcuts.ui:12 msgid "General" msgstr "Algemeen" #: main/data/shortcuts.ui:32 msgid "Navigation" msgstr "Navigatie" #: main/data/shortcuts.ui:37 msgid "Jump to next conversation" msgstr "Verspring naar volgend gesprek" #: main/data/shortcuts.ui:44 msgid "Jump to previous conversation" msgstr "Verspring naar vorige gesprek" #: main/data/global_search.ui:37 msgid "No active search" msgstr "Geen actieve zoekopdracht" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Typ om te beginnen zoeken" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Geen overeenkomende berichten" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Controleer de spelling of probeer filters te verwijderen" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "Klik hier om een gesprek te beginnen of een kanaal te openen." #: main/data/unified_main_content.ui:147 msgid "You have no open chats" msgstr "Je hebt geen open chats" #~ msgid "Failed connecting to %s" #~ msgstr "Verbinden met %s mislukt" #~ msgid "Join Conference" #~ msgstr "Deelnemen aan groepsgesprek" #~ msgid "File" #~ msgstr "Bestand" #~ msgid "Communicate happiness." #~ msgstr "Communiceer geluk." #~ msgid "Preferences" #~ msgstr "Voorkeuren" #~ msgid "Quit" #~ msgstr "Afsluiten" #~ msgid "JID should be of the form โ€œuser@example.comโ€" #~ msgstr "JID moet de vorm โ€˜gebruiker@voorbeeld.nlโ€™ volgen" #~ msgid "Copy Link Address" #~ msgstr "Verwijzingsadres kopiรซren" #~ msgid "Copy" #~ msgstr "Kopiรซren" #~ msgid "Select All" #~ msgstr "Alles selecteren" #~ msgid "Search" #~ msgstr "Zoeken" #~ msgid "Send message marker" #~ msgstr "Berichtmarkers sturen" #~ msgid "Start Chat" #~ msgstr "Gesprek beginnen" #~ msgid "Request presence updates" #~ msgstr "Vraag om aanwezigheidsupdates" #~ msgid "This is an app-notification. Click the button to dismiss" #~ msgstr "Dit is een melding. Klik om te sluiten" #~ msgid "Join on startup" #~ msgstr "Deelnemen bij opstarten" #~ msgid "Add Chat" #~ msgstr "Gesprek beginnen" dino-0.1.0/main/po/nl_BE.po0000644000000000000000000005747713614354364014055 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2019-01-12 20:06+0000\n" "Language-Team: Flemish \n" "Language: nl_BE\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.4-dev\n" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "Image sent" msgstr "Afbeelding verzonden" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "File sent" msgstr "Bestand verzonden" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "Image received" msgstr "Afbeelding ontvangen" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "File received" msgstr "Bestand ontvangen" #: main/src/ui/notifications.vala:98 msgid "Subscription request" msgstr "Abonneringsverzoek" #: main/src/ui/notifications.vala:105 main/src/ui/notifications.vala:140 #: main/src/ui/conversation_summary/subscription_notification.vala:37 msgid "Accept" msgstr "Toestaan" #: main/src/ui/notifications.vala:106 main/src/ui/notifications.vala:139 #: main/src/ui/conversation_summary/subscription_notification.vala:38 msgid "Deny" msgstr "Afwijzen" #: main/src/ui/notifications.vala:112 #: main/src/ui/manage_accounts/add_account_dialog.vala:258 #: main/src/ui/add_conversation/conference_details_fragment.vala:188 #, c-format msgid "Could not connect to %s" msgstr "Kon geen verbinding maken met %s" #: main/src/ui/notifications.vala:128 #, c-format msgid "Invitation to %s" msgstr "" #: main/src/ui/notifications.vala:129 #, c-format msgid "%s invited you to %s" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Account %s verwijderen?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Verwijderen" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Kiest nen avatar" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Selecteren" #: main/src/ui/manage_accounts/dialog.vala:152 main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Annuleren" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Afbeeldingen" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Alle bestanden" #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connectingโ€ฆ" msgstr "Verbinding makenโ€ฆ" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Verbonden" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Verbinding verbroken" #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Verkeerd paswoord" #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Ongeldig TLS-certificaat" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Fout" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Account toevoegen" #: main/src/ui/manage_accounts/add_account_dialog.vala:166 #, c-format msgid "Sign in to %s" msgstr "Inloggen bij %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:210 #, c-format msgid "You can now start using %s" msgstr "Ge kunt %s nu beginnen gebruiken" #: main/src/ui/manage_accounts/add_account_dialog.vala:263 #: main/src/ui/manage_accounts/add_account_dialog.vala:296 #: main/src/ui/manage_accounts/add_account_dialog.vala:306 #: main/src/ui/manage_accounts/add_account_dialog.vala:382 #: main/src/ui/add_conversation/conference_details_fragment.vala:191 msgid "Invalid address" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:278 msgid "Wrong username or password" msgstr "Verkeerde gebruikersnaam of paswoord" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Something went wrong" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:321 msgid "No response from server" msgstr "Geen antwoord van server" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 #, c-format msgid "Register on %s" msgstr "Registreren bij %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:330 msgid "The server requires to sign up through a website" msgstr "De server vereist registratie via ne website" #: main/src/ui/manage_accounts/add_account_dialog.vala:332 msgid "Open Registration" msgstr "Open registratie" #: main/src/ui/manage_accounts/add_account_dialog.vala:344 msgid "Register" msgstr "Registreren" #: main/src/ui/manage_accounts/add_account_dialog.vala:346 #, c-format msgid "Check %s for information on how to sign up" msgstr "Bekijkt %s voor meer informatie over hoe te registreren" #: main/src/ui/conversation_selector/conversation_selector_row.vala:146 #: main/src/ui/conversation_selector/conversation_selector_row.vala:160 #: main/src/ui/util/helper.vala:136 main/src/ui/util/helper.vala:151 msgid "Me" msgstr "Ik" #: main/src/ui/conversation_selector/conversation_selector_row.vala:258 #: main/src/ui/conversation_summary/date_separator_populator.vala:100 #, no-c-format msgid "%b %d" msgstr "%d %b" #: main/src/ui/conversation_selector/conversation_selector_row.vala:262 #: main/src/ui/conversation_summary/date_separator_populator.vala:91 msgid "Yesterday" msgstr "Gisteren" #: main/src/ui/conversation_selector/conversation_selector_row.vala:265 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:205 #, no-c-format msgid "%Hโˆถ%M" msgstr "%Hu%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:266 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:206 #, no-c-format msgid "%lโˆถ%M %p" msgstr "%lu%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:269 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:210 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i min geleden" msgstr[1] "%i min geleden" #: main/src/ui/conversation_selector/conversation_selector_row.vala:271 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:212 msgid "Just now" msgstr "Zojuist" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/unified_window.vala:249 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Deelnemen aan kanaal" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 msgid "Join" msgstr "Deelnemen" #: main/src/ui/util/helper.vala:126 #, c-format msgid "%s from %s" msgstr "" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:28 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Toevoegen" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:42 msgid "Save" msgstr "Opslaan" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Volgende" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Terug" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/src/ui/unified_window.vala:248 #: main/data/conversation_list_titlebar_csd.ui:12 #: main/data/conversation_list_titlebar.ui:16 main/data/menu_add.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:20 main/data/shortcuts.ui:17 msgid "Start Conversation" msgstr "Gesprek beginnen" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Beginnen" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joiningโ€ฆ" msgstr "Deelnemenโ€ฆ" #: main/src/ui/add_conversation/conference_details_fragment.vala:169 msgid "Password required to enter room" msgstr "Paswoord voor groepsgesprek vereist" #: main/src/ui/add_conversation/conference_details_fragment.vala:174 msgid "Banned from joining or creating conference" msgstr "Deelnemen aan of aanmaken van groepsgesprekken verboden" #: main/src/ui/add_conversation/conference_details_fragment.vala:176 msgid "Room does not exist" msgstr "Groepsgesprek bestaat niet" #: main/src/ui/add_conversation/conference_details_fragment.vala:178 msgid "Not allowed to create room" msgstr "Aanmaken van groepsgesprek niet toegestaan" #: main/src/ui/add_conversation/conference_details_fragment.vala:180 msgid "Members-only room" msgstr "Groepsgesprek is alleen-leden" #: main/src/ui/add_conversation/conference_details_fragment.vala:183 msgid "Choose a different nick" msgstr "Kiest nen anderen bijnaam" #: main/src/ui/add_conversation/conference_details_fragment.vala:185 msgid "Too many occupants in room" msgstr "Groepsgesprek heeft te veel deelnemers" #: main/src/ui/unified_window.vala:227 msgid "Welcome to Dino!" msgstr "Welkom bij Dino!" #: main/src/ui/unified_window.vala:228 msgid "Sign in or create an account to get started." msgstr "" #: main/src/ui/unified_window.vala:229 msgid "Set up account" msgstr "Account instellen" #: main/src/ui/unified_window.vala:237 msgid "No active accounts" msgstr "Geen accounts actief" #: main/src/ui/unified_window.vala:238 msgid "Manage accounts" msgstr "Accounts beheren" #: main/src/ui/unified_window.vala:247 msgid "No active conversations" msgstr "Geen gesprekken actief" #: main/src/ui/conversation_summary/file_widget.vala:275 #, c-format msgid "Downloading %sโ€ฆ" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:281 #, c-format msgid "%s offered: %s" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:283 #, c-format msgid "File offered: %s" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:285 msgid "File offered" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:290 msgid "File transfer failed" msgstr "" #: main/src/ui/conversation_summary/date_separator_populator.vala:85 msgid "Today" msgstr "Vandaag" #: main/src/ui/conversation_summary/date_separator_populator.vala:98 msgid "%a, %b %d" msgstr "%a, %b %d" #: main/src/ui/conversation_summary/chat_state_populator.vala:145 #, c-format msgid "%s, %s and %i others" msgstr "%s, %s en %i anderen" #: main/src/ui/conversation_summary/chat_state_populator.vala:147 #, c-format msgid "%s, %s and %s" msgstr "%s, %s en %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:149 #, c-format msgid "%s and %s" msgstr "%s en %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:154 msgid "is typingโ€ฆ" msgid_plural "are typingโ€ฆ" msgstr[0] "is aan het typenโ€ฆ" msgstr[1] "zijn aan het typenโ€ฆ" #: main/src/ui/conversation_summary/chat_state_populator.vala:156 msgid "has stopped typing" msgstr "is gestopt met typen" #: main/src/ui/conversation_summary/content_item_widget_factory.vala:59 msgid "Message too long" msgstr "Bericht te lang" #: main/src/ui/conversation_summary/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Dit contact wilt u toevoegen aan zijn/haar contacten" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:193 #, no-c-format msgid "%x, %Hโˆถ%M" msgstr "%x, %Hu%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:194 #, no-c-format msgid "%x, %lโˆถ%M %p" msgstr "%x, %lu%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:197 #, no-c-format msgid "%b %d, %Hโˆถ%M" msgstr "%d %b, %Hu%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:198 #, no-c-format msgid "%b %d, %lโˆถ%M %p" msgstr "%d %b, %lu%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:201 #, no-c-format msgid "%a, %Hโˆถ%M" msgstr "%a, %Hu%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:202 #, no-c-format msgid "%a, %lโˆถ%M %p" msgstr "%a, %lu%M %p" #: main/src/ui/occupant_menu/list.vala:102 msgid "Owner" msgstr "Eigenaar" #: main/src/ui/occupant_menu/list.vala:104 msgid "Admin" msgstr "Beheerder" #: main/src/ui/occupant_menu/list.vala:106 msgid "Member" msgstr "Lid" #: main/src/ui/occupant_menu/list.vala:108 msgid "User" msgstr "Gebruiker" #: main/src/ui/occupant_menu/view.vala:29 #: main/src/ui/occupant_menu/view.vala:38 msgid "Invite" msgstr "Uitnodigen" #: main/src/ui/occupant_menu/view.vala:37 msgid "Invite to Conference" msgstr "Uitnodigen in groepsgesprek" #: main/src/ui/occupant_menu/view.vala:88 msgid "Start private conversation" msgstr "Privรฉgesprek beginnen" #: main/src/ui/occupant_menu/view.vala:96 msgid "Kick" msgstr "Schoppen" #: main/src/ui/global_search.vala:140 #, c-format msgid "%i search results" msgstr "%i zoekresultaten" #: main/src/ui/global_search.vala:167 #, c-format msgid "In %s" msgstr "In %s" #: main/src/ui/global_search.vala:167 #, c-format msgid "With %s" msgstr "Met %s" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Groepsgespreksgegevens" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:24 msgid "Contact Details" msgstr "Contactgegevens" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Naam van groepsgesprek" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Beschrijving van groepsgesprek" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Blijvend" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "" "Het groepsgesprek blijft bestaan wanneer dat de laatsten deelnemer het " "verlaat" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Publiek vindbaar" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Deelnemers kunnen onderwerp aanpassen" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Discover real JIDs" msgstr "Zichtbaarheid van JIDโ€™s" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who may discover real JIDs?" msgstr "Wie mag de JIDโ€™s van de deelnemers zien?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 msgid "Password" msgstr "Paswoord" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "Password required for room entry, if any" msgstr "Paswoord voor groepsgesprek, indien nodig" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Gemodereerd" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Enkel deelnemers met stem mogen berichten sturen" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Enkel leden" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Enkel leden mogen deelnemen aan het groepsgesprek" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Berichtgeschiedenis" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "" "Maximaal aantal berichten uit geschiedenis dat het groepsgesprek weergeeft" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Gespreksconfiguratie" #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/src/ui/contact_details/settings_provider.vala:12 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Instellingen" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Blokkeren" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "Communicatie en statusupdates worden in beide richtingen geblokkeerd" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Lokale instellingen" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Aan-het-typen-meldingen sturen" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Leesbevestigingen sturen" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Meldingen" #: main/src/ui/contact_details/settings_provider.vala:82 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:124 msgid "On" msgstr "Aan" #: main/src/ui/contact_details/settings_provider.vala:84 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "Off" msgstr "Uit" #: main/src/ui/contact_details/settings_provider.vala:86 msgid "Only when mentioned" msgstr "Enkel wanneer vermeld" #: main/src/ui/contact_details/settings_provider.vala:88 #: main/src/ui/contact_details/settings_provider.vala:123 #, c-format msgid "Default: %s" msgstr "Standaard: %s" #: main/src/ui/conversation_titlebar/search_entry.vala:11 msgid "Search messages" msgstr "" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Melding tonen bij nieuw bericht" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Smileys omzetten naar emojiโ€™s" #: main/data/manage_accounts/dialog.ui:9 main/data/menu_app.ui:7 msgid "Accounts" msgstr "Accounts" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Lokalen alias" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Geen accounts ingesteld" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Account toevoegen" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Inloggen" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Account aanmaken" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Kon geen beveiligde verbinding opstellen" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Verbinden" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Kiest nen openbare server" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Of voert een serveradres in" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Inloggen" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Kiest nen andere server" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Klaar!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Voltooien" #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Onversleuteld" #: main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "" #: main/data/menu_app.ui:21 msgid "About Dino" msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Modernen XMPP-chatcliรซnt" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino is een moderne, vrije chattoepassing voor uw bureaublad. Ze biedt een " "eenvoudige en betrouwbare Jabber/XMPP-ervaring, met uw privacy in het " "achterhoofd." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Ze ondersteunt eind-tot-eind-versleuteling met OMEMO en OpenPGP, en laat u " "toe privacygerelateerde functies, gelijk leesbevestigingen en typmeldingen, " "in te stellen." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino haalt de geschiedenis op van de server en synchroniseert berichten met " "andere apparaten." #: main/data/im.dino.Dino.appdata.xml.in:16 msgid "Main window with conversations" msgstr "Hoofdvenster met gesprekken" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Account" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Schermnaam" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Alias" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Contact toevoegen" #: main/data/shortcuts.ui:12 msgid "General" msgstr "" #: main/data/shortcuts.ui:32 msgid "Navigation" msgstr "" #: main/data/shortcuts.ui:37 msgid "Jump to next conversation" msgstr "" #: main/data/shortcuts.ui:44 msgid "Jump to previous conversation" msgstr "" #: main/data/global_search.ui:37 msgid "No active search" msgstr "Genen actieve zoekopdracht" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Typt voor te beginnen zoeken" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Geen overeenkomende berichten" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Controleert de spelling of probeert filters te verwijderen" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "" #: main/data/unified_main_content.ui:147 msgid "You have no open chats" msgstr "" #~ msgid "Failed connecting to %s" #~ msgstr "Verbinden met %s mislukt" #~ msgid "Join Conference" #~ msgstr "Deelnemen aan groepsgesprek" #~ msgid "File" #~ msgstr "Bestand" #~ msgid "Communicate happiness." #~ msgstr "Communiceert geluk." #~ msgid "Preferences" #~ msgstr "Voorkeuren" #~ msgid "Quit" #~ msgstr "Afsluiten" #~ msgid "JID should be of the form โ€œuser@example.comโ€" #~ msgstr "JID moet de vorm โ€˜gebruiker@voorbeeld.beโ€™ volgen" #~ msgid "Copy Link Address" #~ msgstr "Verwijzingsadres kopiรซren" #~ msgid "Copy" #~ msgstr "Kopiรซren" #~ msgid "Select All" #~ msgstr "Alles selecteren" #~ msgid "Search" #~ msgstr "Zoeken" #~ msgid "Send message marker" #~ msgstr "Berichtmarkers sturen" #~ msgid "Start Chat" #~ msgstr "Gesprek beginnen" dino-0.1.0/main/po/oc.po0000644000000000000000000005731513614354364013466 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2019-12-11 03:27+0000\n" "Language-Team: none\n" "Language: oc\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" "X-Generator: Weblate 3.10-dev\n" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "Image sent" msgstr "Imatge enviat" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "File sent" msgstr "Fichiรจr enviat" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "Image received" msgstr "Imatge recebuda" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "File received" msgstr "Fichiรจr recebut" #: main/src/ui/notifications.vala:98 msgid "Subscription request" msgstr "Demanda dโ€™abonament" #: main/src/ui/notifications.vala:105 main/src/ui/notifications.vala:140 #: main/src/ui/conversation_summary/subscription_notification.vala:37 msgid "Accept" msgstr "Acceptar" #: main/src/ui/notifications.vala:106 main/src/ui/notifications.vala:139 #: main/src/ui/conversation_summary/subscription_notification.vala:38 msgid "Deny" msgstr "Regetar" #: main/src/ui/notifications.vala:112 #: main/src/ui/manage_accounts/add_account_dialog.vala:258 #: main/src/ui/add_conversation/conference_details_fragment.vala:188 #, c-format msgid "Could not connect to %s" msgstr "Connexion impossibla a %s" #: main/src/ui/notifications.vala:128 #, c-format msgid "Invitation to %s" msgstr "Convit a %s" #: main/src/ui/notifications.vala:129 #, c-format msgid "%s invited you to %s" msgstr "%s vos convidรจt a %s" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Volรจtz suprimir lo compte %sโ€‰?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Suprimir" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Seleccionar lโ€™avatar" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Seleccionar" #: main/src/ui/manage_accounts/dialog.vala:152 main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Anullar" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Imatges" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Totes los fichiรจrs" #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connectingโ€ฆ" msgstr "Connexionโ€ฆ" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Connectat" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Desconnectat" #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Marrit senhal" #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Lo certificat TLS es invalid" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Error" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Ajustar un compte" #: main/src/ui/manage_accounts/add_account_dialog.vala:166 #, c-format msgid "Sign in to %s" msgstr "Se connectar a %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:210 #, c-format msgid "You can now start using %s" msgstr "Podรจtz ara comenรงar dโ€™utilizar %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:263 #: main/src/ui/manage_accounts/add_account_dialog.vala:296 #: main/src/ui/manage_accounts/add_account_dialog.vala:306 #: main/src/ui/manage_accounts/add_account_dialog.vala:382 #: main/src/ui/add_conversation/conference_details_fragment.vala:191 msgid "Invalid address" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:278 msgid "Wrong username or password" msgstr "Marrit nom dโ€™utilizaire o senhal" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Something went wrong" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:321 msgid "No response from server" msgstr "Cap de responsa del servidor" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 #, c-format msgid "Register on %s" msgstr "Se marcar sus %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:330 msgid "The server requires to sign up through a website" msgstr "Lo servidor requesรญs una inscripcion via lo site web" #: main/src/ui/manage_accounts/add_account_dialog.vala:332 msgid "Open Registration" msgstr "Inscripcion dubรจrtas" #: main/src/ui/manage_accounts/add_account_dialog.vala:344 msgid "Register" msgstr "Se marcar" #: main/src/ui/manage_accounts/add_account_dialog.vala:346 #, c-format msgid "Check %s for information on how to sign up" msgstr "Consultatz %s per mai dโ€™informacions tocant cossรญ se marchar" #: main/src/ui/conversation_selector/conversation_selector_row.vala:146 #: main/src/ui/conversation_selector/conversation_selector_row.vala:160 #: main/src/ui/util/helper.vala:136 main/src/ui/util/helper.vala:151 msgid "Me" msgstr "Ieu" #: main/src/ui/conversation_selector/conversation_selector_row.vala:258 #: main/src/ui/conversation_summary/date_separator_populator.vala:100 #, no-c-format msgid "%b %d" msgstr "%b %d" #: main/src/ui/conversation_selector/conversation_selector_row.vala:262 #: main/src/ui/conversation_summary/date_separator_populator.vala:91 msgid "Yesterday" msgstr "Iรจr" #: main/src/ui/conversation_selector/conversation_selector_row.vala:265 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:205 #, no-c-format msgid "%Hโˆถ%M" msgstr "%Hโˆถ%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:266 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:206 #, no-c-format msgid "%lโˆถ%M %p" msgstr "%lโˆถ%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:269 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:210 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "fa %i minuta" msgstr[1] "fa %i minutas" #: main/src/ui/conversation_selector/conversation_selector_row.vala:271 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:212 msgid "Just now" msgstr "Ara meteis" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/unified_window.vala:249 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Rejรณnher una sala" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 msgid "Join" msgstr "Rejรณnher" #: main/src/ui/util/helper.vala:126 #, c-format msgid "%s from %s" msgstr "%s de %s" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:28 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Ajustar" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:42 msgid "Save" msgstr "Enregistrar" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Seguent" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Tornar" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/src/ui/unified_window.vala:248 #: main/data/conversation_list_titlebar_csd.ui:12 #: main/data/conversation_list_titlebar.ui:16 main/data/menu_add.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:20 main/data/shortcuts.ui:17 msgid "Start Conversation" msgstr "Comenรงar la discussion" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Comenรงar" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joiningโ€ฆ" msgstr "Connexion a la salaโ€ฆ" #: main/src/ui/add_conversation/conference_details_fragment.vala:169 msgid "Password required to enter room" msgstr "Senhal requesit per dintrar dins la sala" #: main/src/ui/add_conversation/conference_details_fragment.vala:174 msgid "Banned from joining or creating conference" msgstr "Sรจtz forabandit e podรจtz pas participar o crear de conferรฉncias" #: main/src/ui/add_conversation/conference_details_fragment.vala:176 msgid "Room does not exist" msgstr "La sala existรญs pas" #: main/src/ui/add_conversation/conference_details_fragment.vala:178 msgid "Not allowed to create room" msgstr "Creacion de sala defenduda" #: main/src/ui/add_conversation/conference_details_fragment.vala:180 msgid "Members-only room" msgstr "Sala solament pels membres" #: main/src/ui/add_conversation/conference_details_fragment.vala:183 msgid "Choose a different nick" msgstr "Causissรจtz un escais-nom diferent" #: main/src/ui/add_conversation/conference_details_fragment.vala:185 msgid "Too many occupants in room" msgstr "Trรฒp de participants a la sala" #: main/src/ui/unified_window.vala:227 msgid "Welcome to Dino!" msgstr "La benvenguda a Dinoโ€‰!" #: main/src/ui/unified_window.vala:228 msgid "Sign in or create an account to get started." msgstr "Connectatz-vos o creatz un compte per comenรงar." #: main/src/ui/unified_window.vala:229 msgid "Set up account" msgstr "Configurar lo compte" #: main/src/ui/unified_window.vala:237 msgid "No active accounts" msgstr "I a pas cap de compte actiu" #: main/src/ui/unified_window.vala:238 msgid "Manage accounts" msgstr "Gerir los comptes" #: main/src/ui/unified_window.vala:247 msgid "No active conversations" msgstr "Cap de discussion activa" #: main/src/ui/conversation_summary/file_widget.vala:275 #, c-format msgid "Downloading %sโ€ฆ" msgstr "Telecargament %sโ€ฆ" #: main/src/ui/conversation_summary/file_widget.vala:281 #, c-format msgid "%s offered: %s" msgstr "%s a ofrit : %s" #: main/src/ui/conversation_summary/file_widget.vala:283 #, c-format msgid "File offered: %s" msgstr "Fichiรจr ofrit : %s" #: main/src/ui/conversation_summary/file_widget.vala:285 msgid "File offered" msgstr "Fichiรจr ofrit" #: main/src/ui/conversation_summary/file_widget.vala:290 msgid "File transfer failed" msgstr "Fracร s del transferiment de fichiรจr" #: main/src/ui/conversation_summary/date_separator_populator.vala:85 msgid "Today" msgstr "Uรจi" #: main/src/ui/conversation_summary/date_separator_populator.vala:98 msgid "%a, %b %d" msgstr "%a, %b %d" #: main/src/ui/conversation_summary/chat_state_populator.vala:145 #, c-format msgid "%s, %s and %i others" msgstr "%s, %s e %i autres" #: main/src/ui/conversation_summary/chat_state_populator.vala:147 #, c-format msgid "%s, %s and %s" msgstr "%s, %s e %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:149 #, c-format msgid "%s and %s" msgstr "%s e %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:154 msgid "is typingโ€ฆ" msgid_plural "are typingโ€ฆ" msgstr[0] "es a escriureโ€ฆ" msgstr[1] "son a escriureโ€ฆ" #: main/src/ui/conversation_summary/chat_state_populator.vala:156 msgid "has stopped typing" msgstr "a quitat dโ€˜escriure" #: main/src/ui/conversation_summary/content_item_widget_factory.vala:59 msgid "Message too long" msgstr "Lo messatge es trรฒp long" #: main/src/ui/conversation_summary/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Aqueste contacte volriรก vos ajustar a sa lista de contactes" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:193 #, no-c-format msgid "%x, %Hโˆถ%M" msgstr "%x, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:194 #, no-c-format msgid "%x, %lโˆถ%M %p" msgstr "%x, %lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:197 #, no-c-format msgid "%b %d, %Hโˆถ%M" msgstr "%b %d, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:198 #, no-c-format msgid "%b %d, %lโˆถ%M %p" msgstr "%b %d, %lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:201 #, no-c-format msgid "%a, %Hโˆถ%M" msgstr "%a, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:202 #, no-c-format msgid "%a, %lโˆถ%M %p" msgstr "%a, %lโˆถ%M %p" #: main/src/ui/occupant_menu/list.vala:102 msgid "Owner" msgstr "Proprietari" #: main/src/ui/occupant_menu/list.vala:104 msgid "Admin" msgstr "Administrador" #: main/src/ui/occupant_menu/list.vala:106 msgid "Member" msgstr "Membre" #: main/src/ui/occupant_menu/list.vala:108 msgid "User" msgstr "Utilizaire" #: main/src/ui/occupant_menu/view.vala:29 #: main/src/ui/occupant_menu/view.vala:38 msgid "Invite" msgstr "Convidar" #: main/src/ui/occupant_menu/view.vala:37 msgid "Invite to Conference" msgstr "Convidar a la conferรฉncia" #: main/src/ui/occupant_menu/view.vala:88 msgid "Start private conversation" msgstr "Comenรงar una discussion privada" #: main/src/ui/occupant_menu/view.vala:96 msgid "Kick" msgstr "Expulsar" #: main/src/ui/global_search.vala:140 #, c-format msgid "%i search results" msgstr "%i resultats de recรจrca" #: main/src/ui/global_search.vala:167 #, c-format msgid "In %s" msgstr "En %s" #: main/src/ui/global_search.vala:167 #, c-format msgid "With %s" msgstr "Amb %s" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Detalhs de la conferรฉncia" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:24 msgid "Contact Details" msgstr "Detalhs del contacte" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Nom de la sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Descripcion de la sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Persistent" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "La sala demorarร  aprรจs la sortida del darriรจr ocupant" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Discussion publica" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Los ocupants pรฒdon cambiar lo subjรจcte" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Discover real JIDs" msgstr "Descobรจrta dels JID reals" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who may discover real JIDs?" msgstr "Qual pรฒt descobrir los JID realsโ€‰?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 msgid "Password" msgstr "Senhal" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "Password required for room entry, if any" msgstr "Senhal requesit per dintrar a la sala, se cal" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderada" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Solament los ocupant amb votz pรฒdon enviar de messatges" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Membres solament" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Solament los membres pรฒdon dintrar a la sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Istoric dels messatges" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Nombre maximum de messatge tornats per la sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Configuracion de la sala" #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/src/ui/contact_details/settings_provider.vala:12 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Paramรจtres" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Blocar" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "" "Totes los escambis de messatges e dโ€™estat son blocats dins las doas " "direccions" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Paramรจtres locals" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Enviar una notificacion quand escrivรจtz" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Enviar un acusat de lectura" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Notificacions" #: main/src/ui/contact_details/settings_provider.vala:82 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:124 msgid "On" msgstr "Activat" #: main/src/ui/contact_details/settings_provider.vala:84 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "Off" msgstr "Desactivat" #: main/src/ui/contact_details/settings_provider.vala:86 msgid "Only when mentioned" msgstr "Solament quand vos mencionan" #: main/src/ui/contact_details/settings_provider.vala:88 #: main/src/ui/contact_details/settings_provider.vala:123 #, c-format msgid "Default: %s" msgstr "Per defautโ€‰: %s" #: main/src/ui/conversation_titlebar/search_entry.vala:11 msgid "Search messages" msgstr "Cercar de messatges" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Membres" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Mโ€™avisar quand un nรฒu messatge arriba" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Convertir los ยซ smileys ยป en emojis" #: main/data/manage_accounts/dialog.ui:9 main/data/menu_app.ui:7 msgid "Accounts" msgstr "Comptes" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Alias local" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Cap de compte pas configurat" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Ajustar un compte" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Sโ€™identificar" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Crear un compte" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Establiment impossible dโ€™una connexion segura" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Connexion" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Causissรจtz un servidor public" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "O indicatz lโ€™adreรงa dโ€™un servidor" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Connectatz-vos allรฒc" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Causissรจtz un autre servidor" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Tot es prรจstโ€‰!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Terminar" #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Pas chifrat" #: main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "" #: main/data/menu_app.ui:21 msgid "About Dino" msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Client XMPP modรจrn" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino es un client de chat liure e modรจrn per lโ€™ordenador de burรจu. Ensaja de " "provesir una experiรฉncia neta e fisabla de Jabber/XMPP en tรฉner en compte " "vรฒstra confidencialitat." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Compatible amb lo chiframent OMEMO e OpenPGP del cap a la fin e permet de " "configurar de foncionalitats ligadas amb la confidencialitat coma los " "acusats de lectura e las notificacions dโ€™escritura." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino recupรจra lโ€™istoric del servidor e sincroniza los messatges amb dโ€™autres " "periferics." #: main/data/im.dino.Dino.appdata.xml.in:16 msgid "Main window with conversations" msgstr "Fenรจstra mร ger amb conversacions" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Compte" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Escais-nom" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Alias" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Ajustar un contacte" #: main/data/shortcuts.ui:12 msgid "General" msgstr "" #: main/data/shortcuts.ui:32 msgid "Navigation" msgstr "" #: main/data/shortcuts.ui:37 msgid "Jump to next conversation" msgstr "" #: main/data/shortcuts.ui:44 msgid "Jump to previous conversation" msgstr "" #: main/data/global_search.ui:37 msgid "No active search" msgstr "Cap de recรจrca activa" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Comenรงatz dโ€™escriure per aviar una recรจrca" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Cap de messatge correspondent" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Verificatz lโ€™ortografia o ensajatz de suprimir de filtres" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "Clicatz aquรญ per comenรงar una conversacion o jรณnher una sala." #: main/data/unified_main_content.ui:147 msgid "You have no open chats" msgstr "Avรจtz pas cap conversacion dobรจrta" #~ msgid "Failed connecting to %s" #~ msgstr "Fracร s de la connexion a %s" #~ msgid "Join Conference" #~ msgstr "Rejรณnher la conferรฉncia" #~ msgid "File" #~ msgstr "Fichiรจr" dino-0.1.0/main/po/pl.po0000644000000000000000000006032713614354364013475 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2020-01-28 23:24+0000\n" "Language-Team: Polish \n" "Language: pl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2;\n" "X-Generator: Weblate 3.11-dev\n" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "Image sent" msgstr "Wysล‚ano obraz" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "File sent" msgstr "Wysล‚ano plik" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "Image received" msgstr "Odebrano obraz" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "File received" msgstr "Odebrano plik" #: main/src/ui/notifications.vala:98 msgid "Subscription request" msgstr "Proล›ba o subskrypcjฤ™" #: main/src/ui/notifications.vala:105 main/src/ui/notifications.vala:140 #: main/src/ui/conversation_summary/subscription_notification.vala:37 msgid "Accept" msgstr "Akceptuj" #: main/src/ui/notifications.vala:106 main/src/ui/notifications.vala:139 #: main/src/ui/conversation_summary/subscription_notification.vala:38 msgid "Deny" msgstr "Odmรณw" #: main/src/ui/notifications.vala:112 #: main/src/ui/manage_accounts/add_account_dialog.vala:258 #: main/src/ui/add_conversation/conference_details_fragment.vala:188 #, c-format msgid "Could not connect to %s" msgstr "Nie udaล‚o siฤ™ poล‚ฤ…czyฤ‡ z %s" #: main/src/ui/notifications.vala:128 #, c-format msgid "Invitation to %s" msgstr "Zaproszenie do %s" #: main/src/ui/notifications.vala:129 #, c-format msgid "%s invited you to %s" msgstr "%s zaprosiล‚ ciฤ™ do %s" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Usunฤ…ฤ‡ konto %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Usuล„" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Wybierz awatar" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Wybierz" #: main/src/ui/manage_accounts/dialog.vala:152 main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Anuluj" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Obrazy" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Wszystkie pliki" #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connectingโ€ฆ" msgstr "ลฤ…czenieโ€ฆ" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Poล‚ฤ…czony" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Rozล‚ฤ…czony" #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Bล‚ฤ™dne hasล‚o" #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Nieprawidล‚owy certyfikat TLS" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Bล‚ฤ…d" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Dodaj konto" #: main/src/ui/manage_accounts/add_account_dialog.vala:166 #, c-format msgid "Sign in to %s" msgstr "Zaloguj siฤ™ do %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:210 #, c-format msgid "You can now start using %s" msgstr "Moลผesz teraz uลผywaฤ‡ konto %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:263 #: main/src/ui/manage_accounts/add_account_dialog.vala:296 #: main/src/ui/manage_accounts/add_account_dialog.vala:306 #: main/src/ui/manage_accounts/add_account_dialog.vala:382 #: main/src/ui/add_conversation/conference_details_fragment.vala:191 msgid "Invalid address" msgstr "Niepoprawny adres" #: main/src/ui/manage_accounts/add_account_dialog.vala:278 msgid "Wrong username or password" msgstr "Niepoprawny nick lub hasล‚o" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Something went wrong" msgstr "Coล› siฤ™ nie powiodล‚o" #: main/src/ui/manage_accounts/add_account_dialog.vala:321 msgid "No response from server" msgstr "Brak odpowiedzi z serwera" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 #, c-format msgid "Register on %s" msgstr "Zarejestruj siฤ™ na %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:330 msgid "The server requires to sign up through a website" msgstr "Serwer wymaga rejestracji przez stronฤ™ internetowฤ…" #: main/src/ui/manage_accounts/add_account_dialog.vala:332 msgid "Open Registration" msgstr "Otwรณrz stronฤ™" #: main/src/ui/manage_accounts/add_account_dialog.vala:344 msgid "Register" msgstr "Zarejestruj siฤ™" #: main/src/ui/manage_accounts/add_account_dialog.vala:346 #, c-format msgid "Check %s for information on how to sign up" msgstr "Dowiedz siฤ™ na %s, jak siฤ™ zarejestrowaฤ‡" #: main/src/ui/conversation_selector/conversation_selector_row.vala:146 #: main/src/ui/conversation_selector/conversation_selector_row.vala:160 #: main/src/ui/util/helper.vala:136 main/src/ui/util/helper.vala:151 msgid "Me" msgstr "Ja" #: main/src/ui/conversation_selector/conversation_selector_row.vala:258 #: main/src/ui/conversation_summary/date_separator_populator.vala:100 #, no-c-format msgid "%b %d" msgstr "%d %b" #: main/src/ui/conversation_selector/conversation_selector_row.vala:262 #: main/src/ui/conversation_summary/date_separator_populator.vala:91 msgid "Yesterday" msgstr "Wczoraj" #: main/src/ui/conversation_selector/conversation_selector_row.vala:265 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:205 #, no-c-format msgid "%Hโˆถ%M" msgstr "%Hโˆถ%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:266 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:206 #, no-c-format msgid "%lโˆถ%M %p" msgstr "%lโˆถ%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:269 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:210 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i minuta temu" msgstr[1] "%i minuty temu" msgstr[2] "%i minut temu" #: main/src/ui/conversation_selector/conversation_selector_row.vala:271 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:212 msgid "Just now" msgstr "Przed chwilฤ…" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/unified_window.vala:249 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Doล‚ฤ…cz do kanaล‚u" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 msgid "Join" msgstr "Doล‚ฤ…cz" #: main/src/ui/util/helper.vala:126 #, c-format msgid "%s from %s" msgstr "%s z %s" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:28 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Dodaj" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:42 msgid "Save" msgstr "Zapisz" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Dalej" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Wstecz" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/src/ui/unified_window.vala:248 #: main/data/conversation_list_titlebar_csd.ui:12 #: main/data/conversation_list_titlebar.ui:16 main/data/menu_add.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:20 main/data/shortcuts.ui:17 msgid "Start Conversation" msgstr "Rozpocznij rozmowฤ™" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Rozpocznij" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joiningโ€ฆ" msgstr "Doล‚ฤ…czanieโ€ฆ" #: main/src/ui/add_conversation/conference_details_fragment.vala:169 msgid "Password required to enter room" msgstr "Wstฤ™p do pokoju wymaga hasล‚a" #: main/src/ui/add_conversation/conference_details_fragment.vala:174 msgid "Banned from joining or creating conference" msgstr "Doล‚ฤ…czenie do lub zaล‚oลผenie konferencji zostaล‚o zablokowane" #: main/src/ui/add_conversation/conference_details_fragment.vala:176 msgid "Room does not exist" msgstr "Pokรณj nie istnieje" #: main/src/ui/add_conversation/conference_details_fragment.vala:178 msgid "Not allowed to create room" msgstr "Utworzenie pokoju niedozwolone" #: main/src/ui/add_conversation/conference_details_fragment.vala:180 msgid "Members-only room" msgstr "Pokรณj tylko dla czล‚onkรณw" #: main/src/ui/add_conversation/conference_details_fragment.vala:183 msgid "Choose a different nick" msgstr "Wybierz inny nick" #: main/src/ui/add_conversation/conference_details_fragment.vala:185 msgid "Too many occupants in room" msgstr "Zbyt wielu osรณb w pokoju" #: main/src/ui/unified_window.vala:227 msgid "Welcome to Dino!" msgstr "Witaj w Dino!" #: main/src/ui/unified_window.vala:228 msgid "Sign in or create an account to get started." msgstr "Zaloguj siฤ™ lub zaล‚รณลผ konto, aby rozpoczฤ…ฤ‡." #: main/src/ui/unified_window.vala:229 msgid "Set up account" msgstr "Zaล‚รณลผ konto" #: main/src/ui/unified_window.vala:237 msgid "No active accounts" msgstr "Brak aktywnych kont" #: main/src/ui/unified_window.vala:238 msgid "Manage accounts" msgstr "Zarzฤ…dzaj kontami" #: main/src/ui/unified_window.vala:247 msgid "No active conversations" msgstr "Brak aktywnych rozmรณw" #: main/src/ui/conversation_summary/file_widget.vala:275 #, c-format msgid "Downloading %sโ€ฆ" msgstr "Pobieranie %sโ€ฆ" #: main/src/ui/conversation_summary/file_widget.vala:281 #, c-format msgid "%s offered: %s" msgstr "%s zaoferowane: %s" #: main/src/ui/conversation_summary/file_widget.vala:283 #, c-format msgid "File offered: %s" msgstr "Oferta pliku: %s" #: main/src/ui/conversation_summary/file_widget.vala:285 msgid "File offered" msgstr "Oferta pliku" #: main/src/ui/conversation_summary/file_widget.vala:290 msgid "File transfer failed" msgstr "Przesyล‚anie pliku nie powiodล‚o siฤ™" #: main/src/ui/conversation_summary/date_separator_populator.vala:85 msgid "Today" msgstr "Dzisiaj" #: main/src/ui/conversation_summary/date_separator_populator.vala:98 msgid "%a, %b %d" msgstr "%a, %d %b" #: main/src/ui/conversation_summary/chat_state_populator.vala:145 #, c-format msgid "%s, %s and %i others" msgstr "%s, %s i %i innych" #: main/src/ui/conversation_summary/chat_state_populator.vala:147 #, c-format msgid "%s, %s and %s" msgstr "%s, %s i %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:149 #, c-format msgid "%s and %s" msgstr "%s i %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:154 msgid "is typingโ€ฆ" msgid_plural "are typingโ€ฆ" msgstr[0] "piszeโ€ฆ" msgstr[1] "piszฤ…โ€ฆ" msgstr[2] "piszฤ…โ€ฆ" #: main/src/ui/conversation_summary/chat_state_populator.vala:156 msgid "has stopped typing" msgstr "przestaล‚ pisaฤ‡" #: main/src/ui/conversation_summary/content_item_widget_factory.vala:59 msgid "Message too long" msgstr "Za dล‚uga wiadomoล›ฤ‡" #: main/src/ui/conversation_summary/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Ten kontakt chciaล‚by dodaฤ‡ ciebie do swoich kontaktรณw" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:193 #, no-c-format msgid "%x, %Hโˆถ%M" msgstr "%x, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:194 #, no-c-format msgid "%x, %lโˆถ%M %p" msgstr "%x, %lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:197 #, no-c-format msgid "%b %d, %Hโˆถ%M" msgstr "%d %b, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:198 #, no-c-format msgid "%b %d, %lโˆถ%M %p" msgstr "%d %b, %lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:201 #, no-c-format msgid "%a, %Hโˆถ%M" msgstr "%a, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:202 #, no-c-format msgid "%a, %lโˆถ%M %p" msgstr "%a, %lโˆถ%M %p" #: main/src/ui/occupant_menu/list.vala:102 msgid "Owner" msgstr "Wล‚aล›ciciel" #: main/src/ui/occupant_menu/list.vala:104 msgid "Admin" msgstr "Administrator" #: main/src/ui/occupant_menu/list.vala:106 msgid "Member" msgstr "Czล‚onek" #: main/src/ui/occupant_menu/list.vala:108 msgid "User" msgstr "Uลผytkownik" #: main/src/ui/occupant_menu/view.vala:29 #: main/src/ui/occupant_menu/view.vala:38 msgid "Invite" msgstr "Zaproล›" #: main/src/ui/occupant_menu/view.vala:37 msgid "Invite to Conference" msgstr "Zaproล› do konferencji" #: main/src/ui/occupant_menu/view.vala:88 msgid "Start private conversation" msgstr "Rozpocznij prywatnฤ… rozmowฤ™" #: main/src/ui/occupant_menu/view.vala:96 msgid "Kick" msgstr "Wyrzuฤ‡" #: main/src/ui/global_search.vala:140 #, c-format msgid "%i search results" msgstr "%i wyniki wyszukiwania" #: main/src/ui/global_search.vala:167 #, c-format msgid "In %s" msgstr "W %s" #: main/src/ui/global_search.vala:167 #, c-format msgid "With %s" msgstr "Z %s" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Szczegรณล‚y konferencji" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:24 msgid "Contact Details" msgstr "Szczegรณล‚y kontaktu" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Nazwa pokoju" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Opis pokoju" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Trwaล‚y" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "Pokรณj nie zostanie usuniฤ™ty po opuszczeniu przez ostatniego czล‚onka" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Publicznie wyszukiwalny" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Zmiana tematu moลผliwa przez uลผytkownikรณw" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Discover real JIDs" msgstr "Uprawnienie do wyล›wietlenia JID" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who may discover real JIDs?" msgstr "Kto moลผe widzieฤ‡ JID uลผytkownikรณw?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 msgid "Password" msgstr "Hasล‚o" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "Password required for room entry, if any" msgstr "Hasล‚o, aby ograniczyฤ‡ dostฤ™p do pokoju" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderowany" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Tylko uลผytkownicy z prawem gล‚osu mogฤ… wysyล‚aฤ‡ wiadomoล›ci" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Tylko dla czล‚onkรณw" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Tylko czล‚onkowie mogฤ… doล‚ฤ…czyฤ‡ do pokoju" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Historia wiadomoล›ci" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Maksymalna liczba przechowywanych wiadomoล›ci" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Konfiguracja pokoju" #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/src/ui/contact_details/settings_provider.vala:12 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Ustawienia" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Zablokuj" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "Blokowanie wysyล‚ania statusu oraz wiadomoล›ci w obu kierunkach" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Ustawienia lokalne" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Wysyล‚anie powiadomieล„ o pisaniu" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Wysyล‚anie potwierdzeล„ odczytania" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Powiadomienia" #: main/src/ui/contact_details/settings_provider.vala:82 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:124 msgid "On" msgstr "Wล‚ฤ…czone" #: main/src/ui/contact_details/settings_provider.vala:84 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "Off" msgstr "Wyล‚ฤ…czone" #: main/src/ui/contact_details/settings_provider.vala:86 msgid "Only when mentioned" msgstr "Tylko, gdy wspomniano nick" #: main/src/ui/contact_details/settings_provider.vala:88 #: main/src/ui/contact_details/settings_provider.vala:123 #, c-format msgid "Default: %s" msgstr "Domyล›lnie: %s" #: main/src/ui/conversation_titlebar/search_entry.vala:11 msgid "Search messages" msgstr "Wyszukaj wiadomoล›ci" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Czล‚onkowie" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Powiadom, gdy nadejdzie wiadomoล›ฤ‡" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Zamieniaj tekst na emotikony" #: main/data/manage_accounts/dialog.ui:9 main/data/menu_app.ui:7 msgid "Accounts" msgstr "Konta" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Lokalny nick" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Brak ustawionych kont" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Dodaj konto" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Zaloguj siฤ™" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Zaล‚รณลผ konto" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Nie udaล‚o siฤ™ nawiฤ…zaฤ‡ bezpiecznego poล‚ฤ…czenia" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Poล‚ฤ…cz" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Wybierz serwer publiczny" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Lub wprowadลบ adres serwera" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Przejdลบ do logowania" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Wybierz inny serwer" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Wszystko gotowe!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Zakoล„cz" #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Bez szyfrowania" #: main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Skrรณty klawiszowe" #: main/data/menu_app.ui:21 msgid "About Dino" msgstr "O Dino" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Nowoczesny komunikator XMPP" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino jest nowoczesnym, otwartym komunikatorem. Skupia siฤ™ na prostej " "obsล‚udze sieci Jabber/XMPP dbajฤ…c o twojฤ… prywatnoล›ฤ‡." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Obsล‚uguje szyfrowanie od koล„ca do koล„ca za pomocฤ… OMEMO i OpenPGP, a takลผe " "daje kontrolฤ™ย nad funkcjami wpล‚ywajฤ…cymi na prywatnoล›ฤ‡, jak powiadomienia o " "pisaniu czy odczytaniu wiadomoล›ci." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino pobiera historiฤ™ rozmรณw z serwera i synchronizuje wiadomoล›ci z innymi " "urzฤ…dzeniami." #: main/data/im.dino.Dino.appdata.xml.in:16 msgid "Main window with conversations" msgstr "Gล‚รณwne okno rozmรณw" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Konto" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Nick" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Alias" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Dodaj kontakt" #: main/data/shortcuts.ui:12 msgid "General" msgstr "Ogรณlne" #: main/data/shortcuts.ui:32 msgid "Navigation" msgstr "Nawigacja" #: main/data/shortcuts.ui:37 msgid "Jump to next conversation" msgstr "Przejdลบ do nastฤ™pnej rozmowy" #: main/data/shortcuts.ui:44 msgid "Jump to previous conversation" msgstr "Przejdลบ do poprzedniej rozmowy" #: main/data/global_search.ui:37 msgid "No active search" msgstr "Brak aktywnego wyszukiwania" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Zacznij pisaฤ‡, aby wyszukaฤ‡" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Brak pasujฤ…cych wiadomoล›ci" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Sprawdลบ pisowniฤ™ lub sprรณbuj usunฤ…ฤ‡ filtry" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "Kliknij tutaj, aby rozpoczฤ…ฤ‡ rozmowฤ™ albo doล‚ฤ…czyฤ‡ do kanaล‚u." #: main/data/unified_main_content.ui:147 msgid "You have no open chats" msgstr "Nie masz otwartych czatรณw" #~ msgid "Failed connecting to %s" #~ msgstr "Nie udaล‚o siฤ™ poล‚ฤ…czyฤ‡ z %s" #~ msgid "Join Conference" #~ msgstr "Doล‚ฤ…cz do konferencji" #~ msgid "File" #~ msgstr "Plik" #~ msgid "Preferences" #~ msgstr "Preferencje" #~ msgid "Quit" #~ msgstr "Wyjdลบ" #~ msgid "JID should be of the form โ€œuser@example.comโ€" #~ msgstr "JID powinien byฤ‡ w formie \"uลผytkownik@przykล‚ad.com\"" #~ msgid "Copy Link Address" #~ msgstr "Skopiuj adres odnoล›nika" #~ msgid "Copy" #~ msgstr "Kopiuj" #~ msgid "Select All" #~ msgstr "Wybierz wszystkie" #~ msgid "Search" #~ msgstr "Wyszukaj" #~ msgid "Send message marker" #~ msgstr "Wyล›lij informacjฤ™ o odczytaniu wiadomoล›ci" #~ msgid "Start Chat" #~ msgstr "Rozpocznij rozmowฤ™" dino-0.1.0/main/po/pt_BR.po0000644000000000000000000006063413614354364014071 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2020-01-16 00:21+0000\n" "Language-Team: Portuguese (Brazil) \n" "Language: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" "X-Generator: Weblate 3.11-dev\n" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "Image sent" msgstr "Imagem enviada" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "File sent" msgstr "Arquivo enviado" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "Image received" msgstr "Imagem recebida" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "File received" msgstr "Arquivo recebido" #: main/src/ui/notifications.vala:98 msgid "Subscription request" msgstr "Pedido de assinatura" #: main/src/ui/notifications.vala:105 main/src/ui/notifications.vala:140 #: main/src/ui/conversation_summary/subscription_notification.vala:37 msgid "Accept" msgstr "Aceitar" #: main/src/ui/notifications.vala:106 main/src/ui/notifications.vala:139 #: main/src/ui/conversation_summary/subscription_notification.vala:38 msgid "Deny" msgstr "Negar" #: main/src/ui/notifications.vala:112 #: main/src/ui/manage_accounts/add_account_dialog.vala:258 #: main/src/ui/add_conversation/conference_details_fragment.vala:188 #, c-format msgid "Could not connect to %s" msgstr "Nรฃo foi possรญvel se conectar a %s" #: main/src/ui/notifications.vala:128 #, c-format msgid "Invitation to %s" msgstr "Convite para %s" #: main/src/ui/notifications.vala:129 #, c-format msgid "%s invited you to %s" msgstr "%s te convidou para %s" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Remover conta %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Remover" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Escolher avatar" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Selecionar" #: main/src/ui/manage_accounts/dialog.vala:152 main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Cancelar" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Imagens" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Todos os arquivos" #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connectingโ€ฆ" msgstr "Conectandoโ€ฆ" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Conectado" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Desconectado" #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Senha incorreta" #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Certificado TLS invรกlido" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Erro" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Adicionar conta" #: main/src/ui/manage_accounts/add_account_dialog.vala:166 #, c-format msgid "Sign in to %s" msgstr "Registrar-se em %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:210 #, c-format msgid "You can now start using %s" msgstr "Agora vocรช pode comeรงar a usar %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:263 #: main/src/ui/manage_accounts/add_account_dialog.vala:296 #: main/src/ui/manage_accounts/add_account_dialog.vala:306 #: main/src/ui/manage_accounts/add_account_dialog.vala:382 #: main/src/ui/add_conversation/conference_details_fragment.vala:191 msgid "Invalid address" msgstr "Endereรงo invรกlido" #: main/src/ui/manage_accounts/add_account_dialog.vala:278 msgid "Wrong username or password" msgstr "Nome ou Senha errada" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Something went wrong" msgstr "Algo deu errado" #: main/src/ui/manage_accounts/add_account_dialog.vala:321 msgid "No response from server" msgstr "Sem resposta do servidor" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 #, c-format msgid "Register on %s" msgstr "Registre-se em %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:330 msgid "The server requires to sign up through a website" msgstr "O servidor requer que se inscreva atravรฉs de um site" #: main/src/ui/manage_accounts/add_account_dialog.vala:332 msgid "Open Registration" msgstr "Abrir Site de Inscriรงรฃo" #: main/src/ui/manage_accounts/add_account_dialog.vala:344 msgid "Register" msgstr "Registrar" #: main/src/ui/manage_accounts/add_account_dialog.vala:346 #, c-format msgid "Check %s for information on how to sign up" msgstr "Verifique %s para informaรงรฃo de como se inscrever" #: main/src/ui/conversation_selector/conversation_selector_row.vala:146 #: main/src/ui/conversation_selector/conversation_selector_row.vala:160 #: main/src/ui/util/helper.vala:136 main/src/ui/util/helper.vala:151 msgid "Me" msgstr "Eu" #: main/src/ui/conversation_selector/conversation_selector_row.vala:258 #: main/src/ui/conversation_summary/date_separator_populator.vala:100 #, no-c-format msgid "%b %d" msgstr "%b %d" #: main/src/ui/conversation_selector/conversation_selector_row.vala:262 #: main/src/ui/conversation_summary/date_separator_populator.vala:91 msgid "Yesterday" msgstr "Ontem" #: main/src/ui/conversation_selector/conversation_selector_row.vala:265 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:205 #, no-c-format msgid "%Hโˆถ%M" msgstr "%Hโˆถ%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:266 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:206 #, no-c-format msgid "%lโˆถ%M %p" msgstr "%lโˆถ%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:269 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:210 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i min atrรกs" msgstr[1] "%i min atrรกs" #: main/src/ui/conversation_selector/conversation_selector_row.vala:271 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:212 msgid "Just now" msgstr "Agora hรก pouco" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/unified_window.vala:249 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Juntar-se a um canal" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 msgid "Join" msgstr "Juntar-se" #: main/src/ui/util/helper.vala:126 #, c-format msgid "%s from %s" msgstr "%s de %s" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:28 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Adicionar" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:42 msgid "Save" msgstr "Salvar" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Prรณximo" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Voltar" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/src/ui/unified_window.vala:248 #: main/data/conversation_list_titlebar_csd.ui:12 #: main/data/conversation_list_titlebar.ui:16 main/data/menu_add.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:20 main/data/shortcuts.ui:17 msgid "Start Conversation" msgstr "Iniciar conversa" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Iniciar" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joiningโ€ฆ" msgstr "Juntando-seโ€ฆ" #: main/src/ui/add_conversation/conference_details_fragment.vala:169 msgid "Password required to enter room" msgstr "Senha necessรกria para entrar na sala" #: main/src/ui/add_conversation/conference_details_fragment.vala:174 msgid "Banned from joining or creating conference" msgstr "Proibido de entrar ou criar uma conferรชncia" #: main/src/ui/add_conversation/conference_details_fragment.vala:176 msgid "Room does not exist" msgstr "Sala nรฃo existe" #: main/src/ui/add_conversation/conference_details_fragment.vala:178 msgid "Not allowed to create room" msgstr "Nรฃo permitido criar sala" #: main/src/ui/add_conversation/conference_details_fragment.vala:180 msgid "Members-only room" msgstr "Sala somente para membros" #: main/src/ui/add_conversation/conference_details_fragment.vala:183 msgid "Choose a different nick" msgstr "Escolha um apelido diferente" #: main/src/ui/add_conversation/conference_details_fragment.vala:185 msgid "Too many occupants in room" msgstr "Nรบmero mรกximo da sala" #: main/src/ui/unified_window.vala:227 msgid "Welcome to Dino!" msgstr "Seja bem vindo ao Dino!" #: main/src/ui/unified_window.vala:228 msgid "Sign in or create an account to get started." msgstr "Entre ou crie uma conta para comeรงar." #: main/src/ui/unified_window.vala:229 msgid "Set up account" msgstr "Configurar a conta" #: main/src/ui/unified_window.vala:237 msgid "No active accounts" msgstr "Nenhuma conta ativa" #: main/src/ui/unified_window.vala:238 msgid "Manage accounts" msgstr "Gerenciar contas" #: main/src/ui/unified_window.vala:247 msgid "No active conversations" msgstr "Nenhuma conversa ativa" #: main/src/ui/conversation_summary/file_widget.vala:275 #, c-format msgid "Downloading %sโ€ฆ" msgstr "Fazendo download de %sโ€ฆ" #: main/src/ui/conversation_summary/file_widget.vala:281 #, c-format msgid "%s offered: %s" msgstr "%s ofereceu: %s" #: main/src/ui/conversation_summary/file_widget.vala:283 #, c-format msgid "File offered: %s" msgstr "Arquivo oferecido: %s" #: main/src/ui/conversation_summary/file_widget.vala:285 msgid "File offered" msgstr "Arquivo oferecido" #: main/src/ui/conversation_summary/file_widget.vala:290 msgid "File transfer failed" msgstr "Transferรชncia de arquivo falhou" #: main/src/ui/conversation_summary/date_separator_populator.vala:85 msgid "Today" msgstr "Hoje" #: main/src/ui/conversation_summary/date_separator_populator.vala:98 msgid "%a, %b %d" msgstr "%a, %b %d" #: main/src/ui/conversation_summary/chat_state_populator.vala:145 #, c-format msgid "%s, %s and %i others" msgstr "%s, %s e outros %i" #: main/src/ui/conversation_summary/chat_state_populator.vala:147 #, c-format msgid "%s, %s and %s" msgstr "%s, %s e %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:149 #, c-format msgid "%s and %s" msgstr "%s e %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:154 msgid "is typingโ€ฆ" msgid_plural "are typingโ€ฆ" msgstr[0] "estรก digitandoโ€ฆ" msgstr[1] "estรฃo digitandoโ€ฆ" #: main/src/ui/conversation_summary/chat_state_populator.vala:156 msgid "has stopped typing" msgstr "parou de digitar" #: main/src/ui/conversation_summary/content_item_widget_factory.vala:59 msgid "Message too long" msgstr "Mensagem muito longa" #: main/src/ui/conversation_summary/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Este contato gostaria de adicionรก-lo ร  sua lista de contatos" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:193 #, no-c-format msgid "%x, %Hโˆถ%M" msgstr "%x, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:194 #, no-c-format msgid "%x, %lโˆถ%M %p" msgstr "%x, %lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:197 #, no-c-format msgid "%b %d, %Hโˆถ%M" msgstr "%b %d, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:198 #, no-c-format msgid "%b %d, %lโˆถ%M %p" msgstr "%b %d, %lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:201 #, no-c-format msgid "%a, %Hโˆถ%M" msgstr "%a, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:202 #, no-c-format msgid "%a, %lโˆถ%M %p" msgstr "%a, %lโˆถ%M %p" #: main/src/ui/occupant_menu/list.vala:102 msgid "Owner" msgstr "Dono" #: main/src/ui/occupant_menu/list.vala:104 msgid "Admin" msgstr "Admin" #: main/src/ui/occupant_menu/list.vala:106 msgid "Member" msgstr "Membro" #: main/src/ui/occupant_menu/list.vala:108 msgid "User" msgstr "Usuรกrio" #: main/src/ui/occupant_menu/view.vala:29 #: main/src/ui/occupant_menu/view.vala:38 msgid "Invite" msgstr "Convidar" #: main/src/ui/occupant_menu/view.vala:37 msgid "Invite to Conference" msgstr "Convidar para Conferรชncia" #: main/src/ui/occupant_menu/view.vala:88 msgid "Start private conversation" msgstr "Iniciar conversa privada" #: main/src/ui/occupant_menu/view.vala:96 msgid "Kick" msgstr "Kick" #: main/src/ui/global_search.vala:140 #, c-format msgid "%i search results" msgstr "%i resultado(s) da busca" #: main/src/ui/global_search.vala:167 #, c-format msgid "In %s" msgstr "Em %s" #: main/src/ui/global_search.vala:167 #, c-format msgid "With %s" msgstr "Com %s" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Detalhes da Conferรชncia" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:24 msgid "Contact Details" msgstr "Detalhes do contato" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Nome da sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Descriรงรฃo da sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Persistente" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "A sala vai continuar depois do รบltimo integrante sair" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Publicamente pesquisรกvel" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Integrantes podem mudar o assunto" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Discover real JIDs" msgstr "Descobrir JIDs reais" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who may discover real JIDs?" msgstr "Quem pode encontrar JIDs reais?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 msgid "Password" msgstr "Senha" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "Password required for room entry, if any" msgstr "Senha requerida para entrar na sala, se houver" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderado" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Somente integrantes com voz podem enviar mensagens" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Somente membros" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Somente membros podem entrar na sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Histรณrico de mensagens" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Quantidade mรกxima de histรณrico gerado pela sala" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Configuraรงรฃo da sala" #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/src/ui/contact_details/settings_provider.vala:12 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Configuraรงรตes" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Bloquear" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "Comunicaรงรฃo e atualizaรงรตes de status dos dois lados estรฃo bloqueadas" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Configuraรงรตes locais" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Enviar notificaรงรฃo ao digitar" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Enviar confirmaรงรฃo de leitura" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Notificaรงรตes" #: main/src/ui/contact_details/settings_provider.vala:82 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:124 msgid "On" msgstr "Ligado" #: main/src/ui/contact_details/settings_provider.vala:84 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "Off" msgstr "Desligado" #: main/src/ui/contact_details/settings_provider.vala:86 msgid "Only when mentioned" msgstr "Somente quando mencionado" #: main/src/ui/contact_details/settings_provider.vala:88 #: main/src/ui/contact_details/settings_provider.vala:123 #, c-format msgid "Default: %s" msgstr "Padrรฃo: %s" #: main/src/ui/conversation_titlebar/search_entry.vala:11 msgid "Search messages" msgstr "Buscar mensagens" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Membros" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Notificar quando uma nova mensagem chegar" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Converter smileys para emojis" #: main/data/manage_accounts/dialog.ui:9 main/data/menu_app.ui:7 msgid "Accounts" msgstr "Contas" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Pseudรดnimo local" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Nenhuma conta configurada" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Adicionar conta" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Registrar" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Criar conta" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Nรฃo foi possรญvel estabelecer uma conexรฃo segura" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Conectar" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Escolha um servidor publico" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Ou especifique um endereรงo de servidor" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Entrar em vez disso" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Escolha outro servidor" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Tudo configurado!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Finalizado" #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Nรฃo-criptografado" #: main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Atalhos de Teclado" #: main/data/menu_app.ui:21 msgid "About Dino" msgstr "Sobre Dino" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Moderno cliente de chat XMPP" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino รฉ um moderno chat de cรณdigo aberto para desktop. Ele รฉ focado em prover " "uma transparente e confiรกvel experiรชncia Jabber/XMPP, tendo em mente a sua " "privacidade." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Suporte criptografia ponta a ponta com OMEMO e OpenPGP e permite configurar " "privacidadeโ€”caracterรญsticas relacionadas ร s notificaรงรตes de leitura, " "recebimento e escrita." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino obtรฉm o histรณrico do servidor e sincroniza mensagens com outros " "dispositivos." #: main/data/im.dino.Dino.appdata.xml.in:16 msgid "Main window with conversations" msgstr "Janela principal com as conversas" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Conta" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Apelido" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Pseudรดnimo" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Adicionar contato" #: main/data/shortcuts.ui:12 msgid "General" msgstr "Geral" #: main/data/shortcuts.ui:32 msgid "Navigation" msgstr "Navegaรงรฃo" #: main/data/shortcuts.ui:37 msgid "Jump to next conversation" msgstr "Ir para a prรณxima conversa" #: main/data/shortcuts.ui:44 msgid "Jump to previous conversation" msgstr "Ir para a conversa anterior" #: main/data/global_search.ui:37 msgid "No active search" msgstr "Sem busca ativa" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Escreva para iniciar a busca" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Sem mensagens correspondentes" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Verifique a grafia ou tente remover filtros" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "Clique aqui para inicial uma conversa or entrar em um canal." #: main/data/unified_main_content.ui:147 msgid "You have no open chats" msgstr "Vocรช nรฃo tem conversas abertas" #~ msgid "Failed connecting to %s" #~ msgstr "Falha ao se conectar com %s" #~ msgid "Join Conference" #~ msgstr "Juntar-se a uma conferรชncia" #~ msgid "File" #~ msgstr "Arquivo" #~ msgid "Communicate happiness." #~ msgstr "Comunique-se alegremente." #~ msgid "Preferences" #~ msgstr "Preferรชncias" #~ msgid "Quit" #~ msgstr "Sair" #~ msgid "JID should be of the form โ€œuser@example.comโ€" #~ msgstr "O Jabber ID deve estar no formato \"usuรกrio@exemplo.com\"" #~ msgid "Copy Link Address" #~ msgstr "Copiar endereรงo do link" #~ msgid "Copy" #~ msgstr "Copiar" #~ msgid "Select All" #~ msgstr "Selecionar tudo" #~ msgid "Search" #~ msgstr "Pesquisar" #~ msgid "Send message marker" #~ msgstr "Enviar marcador de mensagem" #~ msgid "Start Chat" #~ msgstr "Iniciar bate-papo" #~ msgid "Request presence updates" #~ msgstr "Pedir atualizaรงรตes sobre presenรงa" #~ msgid "Join on startup" #~ msgstr "Conectar-se ao inicializar" #~ msgid "Add Chat" #~ msgstr "Adicionar bate-papo" dino-0.1.0/main/po/ro.po0000644000000000000000000006110613614354364013476 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2020-01-12 13:21+0000\n" "Language-Team: Romanian \n" "Language: ro\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < " "20)) ? 1 : 2;\n" "X-Generator: Weblate 3.10.1\n" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "Image sent" msgstr "Imagine trimisฤƒ" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "File sent" msgstr "Fiศ™ier trimis" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "Image received" msgstr "Imagine primitรค" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "File received" msgstr "Fiศ™ier primit" #: main/src/ui/notifications.vala:98 msgid "Subscription request" msgstr "Cerere de abonare" #: main/src/ui/notifications.vala:105 main/src/ui/notifications.vala:140 #: main/src/ui/conversation_summary/subscription_notification.vala:37 msgid "Accept" msgstr "Acceptฤƒ" #: main/src/ui/notifications.vala:106 main/src/ui/notifications.vala:139 #: main/src/ui/conversation_summary/subscription_notification.vala:38 msgid "Deny" msgstr "Refuzฤƒ" #: main/src/ui/notifications.vala:112 #: main/src/ui/manage_accounts/add_account_dialog.vala:258 #: main/src/ui/add_conversation/conference_details_fragment.vala:188 #, c-format msgid "Could not connect to %s" msgstr "Nu s-a putut face conectarea la %s" #: main/src/ui/notifications.vala:128 #, c-format msgid "Invitation to %s" msgstr "Invitaลฃie la %s" #: main/src/ui/notifications.vala:129 #, c-format msgid "%s invited you to %s" msgstr "%s v-a invitat la %s" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Se ศ™terge contul %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "ศ˜terge" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Selectare avatar" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Selectare" #: main/src/ui/manage_accounts/dialog.vala:152 main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Anulare" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Imagini" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Toate fiศ™ierele" #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connectingโ€ฆ" msgstr "Conectareโ€ฆ" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Conectat" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Deconectat" #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Parolฤƒ greศ™itฤƒ" #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Certificat TLS invalid" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Eroare" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Adaugฤƒ un cont" #: main/src/ui/manage_accounts/add_account_dialog.vala:166 #, c-format msgid "Sign in to %s" msgstr "Autentificare รฎn %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:210 #, c-format msgid "You can now start using %s" msgstr "Acum puteศ›i utiliza %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:263 #: main/src/ui/manage_accounts/add_account_dialog.vala:296 #: main/src/ui/manage_accounts/add_account_dialog.vala:306 #: main/src/ui/manage_accounts/add_account_dialog.vala:382 #: main/src/ui/add_conversation/conference_details_fragment.vala:191 msgid "Invalid address" msgstr "Adresฤƒ invalidฤƒ" #: main/src/ui/manage_accounts/add_account_dialog.vala:278 msgid "Wrong username or password" msgstr "Nume utilizator sau parolฤƒ greศ™ite" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Something went wrong" msgstr "A apฤƒrut o problemฤƒ" #: main/src/ui/manage_accounts/add_account_dialog.vala:321 msgid "No response from server" msgstr "Nici un rฤƒspuns de la server" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 #, c-format msgid "Register on %s" msgstr "รŽnregistrare pe %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:330 msgid "The server requires to sign up through a website" msgstr "Serverul necesitฤƒ รฎnregistrarea printr-un site web" #: main/src/ui/manage_accounts/add_account_dialog.vala:332 msgid "Open Registration" msgstr "Deschis pentru รฎnregistrare" #: main/src/ui/manage_accounts/add_account_dialog.vala:344 msgid "Register" msgstr "รŽnregistreazฤƒ-te" #: main/src/ui/manage_accounts/add_account_dialog.vala:346 #, c-format msgid "Check %s for information on how to sign up" msgstr "Aflaศ›i pe %s informaศ›ii despre cum se face รฎnregistrarea" #: main/src/ui/conversation_selector/conversation_selector_row.vala:146 #: main/src/ui/conversation_selector/conversation_selector_row.vala:160 #: main/src/ui/util/helper.vala:136 main/src/ui/util/helper.vala:151 msgid "Me" msgstr "Eu" #: main/src/ui/conversation_selector/conversation_selector_row.vala:258 #: main/src/ui/conversation_summary/date_separator_populator.vala:100 #, no-c-format msgid "%b %d" msgstr "%b %d" #: main/src/ui/conversation_selector/conversation_selector_row.vala:262 #: main/src/ui/conversation_summary/date_separator_populator.vala:91 msgid "Yesterday" msgstr "Ieri" #: main/src/ui/conversation_selector/conversation_selector_row.vala:265 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:205 #, no-c-format msgid "%Hโˆถ%M" msgstr "%Hโˆถ%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:266 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:206 #, no-c-format msgid "%lโˆถ%M %p" msgstr "%lโˆถ%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:269 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:210 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "Acum %i minut" msgstr[1] "Acum %i minute" msgstr[2] "Acum %i de minute" #: main/src/ui/conversation_selector/conversation_selector_row.vala:271 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:212 msgid "Just now" msgstr "Chiar acum" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/unified_window.vala:249 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Alฤƒturaศ›i-vฤƒ canalului" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 msgid "Join" msgstr "Alฤƒturaศ›i-vฤƒ" #: main/src/ui/util/helper.vala:126 #, c-format msgid "%s from %s" msgstr "%s de la %s" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:28 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Adaugฤƒ" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:42 msgid "Save" msgstr "Salvare" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Urmฤƒtorul" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "รŽnapoi" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/src/ui/unified_window.vala:248 #: main/data/conversation_list_titlebar_csd.ui:12 #: main/data/conversation_list_titlebar.ui:16 main/data/menu_add.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:20 main/data/shortcuts.ui:17 msgid "Start Conversation" msgstr "Porneศ™te o conversaศ›ie" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Pornire" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joiningโ€ฆ" msgstr "Conectareโ€ฆ" #: main/src/ui/add_conversation/conference_details_fragment.vala:169 msgid "Password required to enter room" msgstr "Este necesarฤƒ o parolฤƒ pentru a te putea alฤƒtura acestei discuศ›ii" #: main/src/ui/add_conversation/conference_details_fragment.vala:174 msgid "Banned from joining or creating conference" msgstr "Aveศ›i interdicศ›ie de a vฤƒ alฤƒtura sau de a crea o conferinศ›ฤƒ" #: main/src/ui/add_conversation/conference_details_fragment.vala:176 msgid "Room does not exist" msgstr "Aceastฤƒ discuศ›ie nu existฤƒ" #: main/src/ui/add_conversation/conference_details_fragment.vala:178 msgid "Not allowed to create room" msgstr "Este interzisฤƒ crearea unei discuศ›ii" #: main/src/ui/add_conversation/conference_details_fragment.vala:180 msgid "Members-only room" msgstr "Discuศ›ie accesibilฤƒ numai membrilor" #: main/src/ui/add_conversation/conference_details_fragment.vala:183 msgid "Choose a different nick" msgstr "Alegeศ›i un nume diferit" #: main/src/ui/add_conversation/conference_details_fragment.vala:185 msgid "Too many occupants in room" msgstr "Discuศ›ia are prea mulศ›i membrii" #: main/src/ui/unified_window.vala:227 msgid "Welcome to Dino!" msgstr "Bine aศ›i venit la Dino!" #: main/src/ui/unified_window.vala:228 msgid "Sign in or create an account to get started." msgstr "Autentificaศ›i-vฤƒ sau creaศ›i un cont pentru a รฎncepe." #: main/src/ui/unified_window.vala:229 msgid "Set up account" msgstr "Configurare cont" #: main/src/ui/unified_window.vala:237 msgid "No active accounts" msgstr "Nici un cont activ" #: main/src/ui/unified_window.vala:238 msgid "Manage accounts" msgstr "Administrare conturi" #: main/src/ui/unified_window.vala:247 msgid "No active conversations" msgstr "Nici o conversaศ›ie activฤƒ" #: main/src/ui/conversation_summary/file_widget.vala:275 #, c-format msgid "Downloading %sโ€ฆ" msgstr "Se descarcฤƒ %sโ€ฆ" #: main/src/ui/conversation_summary/file_widget.vala:281 #, c-format msgid "%s offered: %s" msgstr "%s a oferit: %s" #: main/src/ui/conversation_summary/file_widget.vala:283 #, c-format msgid "File offered: %s" msgstr "Fiศ™ier oferit: %s" #: main/src/ui/conversation_summary/file_widget.vala:285 msgid "File offered" msgstr "Fiศ™ier oferit" #: main/src/ui/conversation_summary/file_widget.vala:290 msgid "File transfer failed" msgstr "Transferul fiศ™ierului a eศ™uat" #: main/src/ui/conversation_summary/date_separator_populator.vala:85 msgid "Today" msgstr "Azi" #: main/src/ui/conversation_summary/date_separator_populator.vala:98 msgid "%a, %b %d" msgstr "%a, %b %d" #: main/src/ui/conversation_summary/chat_state_populator.vala:145 #, c-format msgid "%s, %s and %i others" msgstr "%s, %s ศ™i รฎncฤƒ %i alศ›ii" #: main/src/ui/conversation_summary/chat_state_populator.vala:147 #, c-format msgid "%s, %s and %s" msgstr "%s, %s ศ™i %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:149 #, c-format msgid "%s and %s" msgstr "%s ศ™i %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:154 msgid "is typingโ€ฆ" msgid_plural "are typingโ€ฆ" msgstr[0] "tasteazฤƒโ€ฆ" msgstr[1] "tasteazฤƒโ€ฆ" msgstr[2] "tasteazฤƒโ€ฆ" #: main/src/ui/conversation_summary/chat_state_populator.vala:156 msgid "has stopped typing" msgstr "s-a oprit din scris" #: main/src/ui/conversation_summary/content_item_widget_factory.vala:59 msgid "Message too long" msgstr "Mesaj prea lung" #: main/src/ui/conversation_summary/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Acest contact doreศ™te sฤƒ vฤƒ adauge la lista ei/lui de contacte" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:193 #, no-c-format msgid "%x, %Hโˆถ%M" msgstr "%x, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:194 #, no-c-format msgid "%x, %lโˆถ%M %p" msgstr "%x, %lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:197 #, no-c-format msgid "%b %d, %Hโˆถ%M" msgstr "%b %d, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:198 #, no-c-format msgid "%b %d, %lโˆถ%M %p" msgstr "%b %d, %lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:201 #, no-c-format msgid "%a, %Hโˆถ%M" msgstr "%a, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:202 #, no-c-format msgid "%a, %lโˆถ%M %p" msgstr "%a, %lโˆถ%M %p" #: main/src/ui/occupant_menu/list.vala:102 msgid "Owner" msgstr "Proprietar" #: main/src/ui/occupant_menu/list.vala:104 msgid "Admin" msgstr "Administrator" #: main/src/ui/occupant_menu/list.vala:106 msgid "Member" msgstr "Membru/ฤƒ" #: main/src/ui/occupant_menu/list.vala:108 msgid "User" msgstr "Utilizator" #: main/src/ui/occupant_menu/view.vala:29 #: main/src/ui/occupant_menu/view.vala:38 msgid "Invite" msgstr "Invitฤƒ" #: main/src/ui/occupant_menu/view.vala:37 msgid "Invite to Conference" msgstr "Invitฤƒ la o conferinศ›ฤƒ" #: main/src/ui/occupant_menu/view.vala:88 msgid "Start private conversation" msgstr "Porneศ™te o conversaศ›ie privatฤƒ" #: main/src/ui/occupant_menu/view.vala:96 msgid "Kick" msgstr "Dฤƒ afarฤƒ" #: main/src/ui/global_search.vala:140 #, c-format msgid "%i search results" msgstr "%i rezultate la cฤƒutare" #: main/src/ui/global_search.vala:167 #, c-format msgid "In %s" msgstr "รŽn %s" #: main/src/ui/global_search.vala:167 #, c-format msgid "With %s" msgstr "Cu %s" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Detalii conferinศ›ฤƒ" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:24 msgid "Contact Details" msgstr "Detalii contact" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Numele discuศ›iei" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Descrierea discuศ›iei" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Persistentฤƒ" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "Discuศ›ia va continua sฤƒ existe ศ™i dupฤƒ ce ultima persoanฤƒ va ieศ™i" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Vizibilฤƒ public" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Membrii pot schimba subiectul" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Discover real JIDs" msgstr "Descoperฤƒ adresele adevฤƒrate" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who may discover real JIDs?" msgstr "Cine poate descoperi adresele adevฤƒrate?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 msgid "Password" msgstr "Parolฤƒ" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "Password required for room entry, if any" msgstr "Dacฤƒ existฤƒ, o parolฤƒ va fi necesarฤƒ pentru alฤƒturarea la discuศ›ie" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderatฤƒ" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Doar membrii cu drept de voce vor putea trimite mesaje" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Doar pentru membri" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Numai membrii se pot alฤƒtura discuศ›iei" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Istoric mesaje" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Numฤƒrul maxim de mesaje din istoric ce va fi afiศ™at รฎn discuศ›ie" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Configurare discuศ›ie" #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/src/ui/contact_details/settings_provider.vala:12 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Setฤƒri" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Blocheazฤƒ" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "Comunicaศ›iile ศ™i actualizฤƒrile de stare sunt blocate รฎn ambele sensuri" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Setฤƒri locale" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Trimite notificare la tastare" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Trimite notificare la primire" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Notificฤƒri" #: main/src/ui/contact_details/settings_provider.vala:82 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:124 msgid "On" msgstr "Da" #: main/src/ui/contact_details/settings_provider.vala:84 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "Off" msgstr "Nu" #: main/src/ui/contact_details/settings_provider.vala:86 msgid "Only when mentioned" msgstr "Doar la menศ›iuni" #: main/src/ui/contact_details/settings_provider.vala:88 #: main/src/ui/contact_details/settings_provider.vala:123 #, c-format msgid "Default: %s" msgstr "Implicit: %s" #: main/src/ui/conversation_titlebar/search_entry.vala:11 msgid "Search messages" msgstr "Cautฤƒ mesaje" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Membri" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Notificฤƒ atunci cรขnd este primit un mesaj nou" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Converteศ™te zรขmbilici รฎn emoticoane" #: main/data/manage_accounts/dialog.ui:9 main/data/menu_app.ui:7 msgid "Accounts" msgstr "Conturi" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Alias local" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Nici un cont configurat" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Adaugฤƒ un cont" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Autentificare" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Creeazฤƒ cont" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Nu s-a putut stabili o conexiune securizatฤƒ" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Conectare" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Alegeศ›i un server public" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Sau specificaศ›i adresa serverului" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "รŽnapoi la conectare" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Alege alt server" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Gata!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Finalizare" #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Necriptat" #: main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Scurtฤƒturi de tastaturฤƒ" #: main/data/menu_app.ui:21 msgid "About Dino" msgstr "Despre Dino" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Client XMPP de discuศ›ii modern" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino este un client de chat modern, cu sursฤƒ deschisฤƒ, pentru calculatoare. " "Se concentreazฤƒ sฤƒ furnizeze o experienศ›ฤƒ Jabber/XMPP clarฤƒ ศ™i fiabilฤƒ, " "ศ›inรขnd cont de confidenศ›ialitatea dumneavoastrฤƒ." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Suportฤƒ criptare de la un capฤƒt la altul prin intermediul OMEMO ศ™i OpenPGP, " "ศ™i permite configurarea caracteristicilor legate de confidenศ›ialitate precum " "trimiterea notificฤƒrilor de primire ศ™i tastare." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino preia istoricul discuศ›iilor de pe server ศ™i sincronizeazฤƒ mesajele cu " "celelalte dispozitive." #: main/data/im.dino.Dino.appdata.xml.in:16 msgid "Main window with conversations" msgstr "Fereastra principalฤƒ de conversaศ›ii" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Cont" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Nume" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Alias" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Adaugฤƒ contact" #: main/data/shortcuts.ui:12 msgid "General" msgstr "General" #: main/data/shortcuts.ui:32 msgid "Navigation" msgstr "Navigare" #: main/data/shortcuts.ui:37 msgid "Jump to next conversation" msgstr "Salt la urmฤƒtoarea conversaศ›ie" #: main/data/shortcuts.ui:44 msgid "Jump to previous conversation" msgstr "Salt la conversaศ›ia anterioarฤƒ" #: main/data/global_search.ui:37 msgid "No active search" msgstr "Nici o cฤƒutare activฤƒ" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Tastaศ›i ceva pentru a porni cฤƒutarea" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Nici un mesaj nu se potriveศ™te" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Verificaศ›i ortografia sau รฎncercaศ›i sฤƒ eliminaศ›i filtre" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "Apฤƒsaศ›i aici pentru a porni o conversaศ›ie sau a vฤƒ alฤƒtura unui canal." #: main/data/unified_main_content.ui:147 msgid "You have no open chats" msgstr "Nu aveศ›i nici o discuศ›ie deschisฤƒ" #~ msgid "Failed connecting to %s" #~ msgstr "Eroare la conectarea cu %s" #~ msgid "Join Conference" #~ msgstr "Alฤƒturฤƒ-te unei conferinศ›e" #~ msgid "File" #~ msgstr "Fiศ™ier" #~ msgid "Preferences" #~ msgstr "Preferinศ›e" #~ msgid "Quit" #~ msgstr "Ieศ™ire" #~ msgid "JID should be of the form โ€œuser@example.comโ€" #~ msgstr "JID trebuie sฤƒ fie de forma โ€œutilizator@exemplu.roโ€" #~ msgid "Copy Link Address" #~ msgstr "Copiazฤƒ adresa legฤƒturii" #~ msgid "Copy" #~ msgstr "Copiere" #~ msgid "Select All" #~ msgstr "Selecteazฤƒ tot" #~ msgid "Search" #~ msgstr "Cฤƒutare" #~ msgid "Send message marker" #~ msgstr "Trimite marcajul mesajului" #~ msgid "Start Chat" #~ msgstr "Porneศ™te o discuศ›ie" dino-0.1.0/main/po/ru.po0000644000000000000000000006611713614354364013513 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2020-01-22 22:21+0000\n" "Language-Team: Russian \n" "Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "X-Generator: Weblate 3.11-dev\n" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "Image sent" msgstr "ะ˜ะทะพะฑั€ะฐะถะตะฝะธะต ะพั‚ะฟั€ะฐะฒะปะตะฝะพ" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "File sent" msgstr "ะคะฐะนะป ะพั‚ะฟั€ะฐะฒะปะตะฝ" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "Image received" msgstr "ะŸะพะปัƒั‡ะตะฝะพ ะธะทะพะฑั€ะฐะถะตะฝะธะต" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "File received" msgstr "ะŸะพะปัƒั‡ะตะฝ ั„ะฐะนะป" #: main/src/ui/notifications.vala:98 msgid "Subscription request" msgstr "ะขั€ะตะฑัƒะตั‚ัั ะฐะฒั‚ะพั€ะธะทะฐั†ะธั" #: main/src/ui/notifications.vala:105 main/src/ui/notifications.vala:140 #: main/src/ui/conversation_summary/subscription_notification.vala:37 msgid "Accept" msgstr "ะŸั€ะธะฝัั‚ัŒ" #: main/src/ui/notifications.vala:106 main/src/ui/notifications.vala:139 #: main/src/ui/conversation_summary/subscription_notification.vala:38 msgid "Deny" msgstr "ะžั‚ะบะปะพะฝะธั‚ัŒ" #: main/src/ui/notifications.vala:112 #: main/src/ui/manage_accounts/add_account_dialog.vala:258 #: main/src/ui/add_conversation/conference_details_fragment.vala:188 #, c-format msgid "Could not connect to %s" msgstr "ะะต ัƒะดะฐะปะพััŒ ะฟะพะดะบะปัŽั‡ะธั‚ัŒัั ะบ %s" #: main/src/ui/notifications.vala:128 #, c-format msgid "Invitation to %s" msgstr "ะŸั€ะธะณะปะฐัˆะตะฝะธะต ะดะปั %s" #: main/src/ui/notifications.vala:129 #, c-format msgid "%s invited you to %s" msgstr "%s ะฟั€ะธะณะปะฐัะธะป ะฒะฐั ะฒ %s" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "ะฃะดะฐะปะธั‚ัŒ ะฐะบะบะฐัƒะฝั‚ %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "ะฃะดะฐะปะธั‚ัŒ" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "ะ’ั‹ะฑั€ะฐั‚ัŒ ะฐะฒะฐั‚ะฐั€" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "ะ’ั‹ะฑั€ะฐั‚ัŒ" #: main/src/ui/manage_accounts/dialog.vala:152 main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "ะžั‚ะผะตะฝะฐ" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "ะ˜ะทะพะฑั€ะฐะถะตะฝะธั" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "ะ’ัะต ั„ะฐะนะปั‹" #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connectingโ€ฆ" msgstr "ะŸะพะดะบะปัŽั‡ะตะฝะธะตโ€ฆ" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "ะŸะพะดะบะปัŽั‡ะตะฝะพ" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "ะžั‚ะบะปัŽั‡ะตะฝะพ" #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "ะะตะฒะตั€ะฝั‹ะน ะฟะฐั€ะพะปัŒ" #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "ะะตะดะตะนัั‚ะฒะธั‚ะตะปัŒะฝั‹ะน TSL ัะตั€ั‚ะธั„ะธะบะฐั‚" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "ะžัˆะธะฑะบะฐ" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "ะ”ะพะฑะฐะฒะธั‚ัŒ ะฐะบะบะฐัƒะฝั‚" #: main/src/ui/manage_accounts/add_account_dialog.vala:166 #, c-format msgid "Sign in to %s" msgstr "ะ’ั…ะพะด ะฒ %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:210 #, c-format msgid "You can now start using %s" msgstr "ะขะตะฟะตั€ัŒ ะฒั‹ ะผะพะถะตั‚ะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:263 #: main/src/ui/manage_accounts/add_account_dialog.vala:296 #: main/src/ui/manage_accounts/add_account_dialog.vala:306 #: main/src/ui/manage_accounts/add_account_dialog.vala:382 #: main/src/ui/add_conversation/conference_details_fragment.vala:191 msgid "Invalid address" msgstr "ะะตะดะตะนัั‚ะฒะธั‚ะตะปัŒะฝั‹ะน ะฐะดั€ะตั" #: main/src/ui/manage_accounts/add_account_dialog.vala:278 msgid "Wrong username or password" msgstr "ะะตะฟั€ะฐะฒะธะปัŒะฝั‹ะน ะปะพะณะธะฝ ะธะปะธ ะฟะฐั€ะพะปัŒ" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Something went wrong" msgstr "ะงั‚ะพ-ั‚ะพ ะฟะพัˆะปะพ ะฝะต ั‚ะฐะบ" #: main/src/ui/manage_accounts/add_account_dialog.vala:321 msgid "No response from server" msgstr "ะะตั‚ ะพั‚ะฒะตั‚ะฐ ะพั‚ ัะตั€ะฒะตั€ะฐ" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 #, c-format msgid "Register on %s" msgstr "ะ ะตะณะธัั‚ั€ะฐั†ะธั ะฒ %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:330 msgid "The server requires to sign up through a website" msgstr "ะกะตั€ะฒะตั€ ั‚ั€ะตะฑัƒะตั‚ ะฐะฒั‚ะพั€ะธะทะฐั†ะธัŽ ั‡ะตั€ะตะท ัะฐะนั‚" #: main/src/ui/manage_accounts/add_account_dialog.vala:332 msgid "Open Registration" msgstr "ะžั‚ะบั€ั‹ั‚ะฐั ั€ะตะณะธัั‚ั€ะฐั†ะธั" #: main/src/ui/manage_accounts/add_account_dialog.vala:344 msgid "Register" msgstr "ะ—ะฐั€ะตะณะธัั‚ั€ะธั€ะพะฒะฐั‚ัŒัั" #: main/src/ui/manage_accounts/add_account_dialog.vala:346 #, c-format msgid "Check %s for information on how to sign up" msgstr "ะŸั€ะพั‡ั‚ะธั‚ะต %s, ั‡ั‚ะพะฑั‹ ัƒะทะฝะฐั‚ัŒ ะพ ะฟั€ะพั†ะตััะต ะฐะฒั‚ะพั€ะธะทะฐั†ะธะธ" #: main/src/ui/conversation_selector/conversation_selector_row.vala:146 #: main/src/ui/conversation_selector/conversation_selector_row.vala:160 #: main/src/ui/util/helper.vala:136 main/src/ui/util/helper.vala:151 msgid "Me" msgstr "ะฏ" #: main/src/ui/conversation_selector/conversation_selector_row.vala:258 #: main/src/ui/conversation_summary/date_separator_populator.vala:100 #, no-c-format msgid "%b %d" msgstr "%b %d" #: main/src/ui/conversation_selector/conversation_selector_row.vala:262 #: main/src/ui/conversation_summary/date_separator_populator.vala:91 msgid "Yesterday" msgstr "ะ’ั‡ะตั€ะฐ" #: main/src/ui/conversation_selector/conversation_selector_row.vala:265 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:205 #, no-c-format msgid "%Hโˆถ%M" msgstr "%Hโˆถ%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:266 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:206 #, no-c-format msgid "%lโˆถ%M %p" msgstr "%lโˆถ%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:269 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:210 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i ะผะธะฝัƒั‚ัƒ ะฝะฐะทะฐะด" msgstr[1] "%i ะผะธะฝัƒั‚ ะฝะฐะทะฐะด" msgstr[2] "%i ะผะธะฝัƒั‚ ะฝะฐะทะฐะด" #: main/src/ui/conversation_selector/conversation_selector_row.vala:271 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:212 msgid "Just now" msgstr "ะขะพะปัŒะบะพ ั‡ั‚ะพ" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/unified_window.vala:249 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "ะ’ะพะนั‚ะธ ะฒ ะบะฐะฝะฐะป" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 msgid "Join" msgstr "ะ’ะพะนั‚ะธ" #: main/src/ui/util/helper.vala:126 #, c-format msgid "%s from %s" msgstr "%s ะธะท %s" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:28 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "ะ”ะพะฑะฐะฒะธั‚ัŒ" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:42 msgid "Save" msgstr "ะกะพั…ั€ะฐะฝะธั‚ัŒ" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "ะกะปะตะดัƒัŽั‰ะตะต" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "ะะฐะทะฐะด" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/src/ui/unified_window.vala:248 #: main/data/conversation_list_titlebar_csd.ui:12 #: main/data/conversation_list_titlebar.ui:16 main/data/menu_add.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:20 main/data/shortcuts.ui:17 msgid "Start Conversation" msgstr "ะะฐั‡ะฐั‚ัŒ ั‡ะฐั‚" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "ะะฐั‡ะฐั‚ัŒ" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joiningโ€ฆ" msgstr "ะŸั€ะธัะพะตะดะธะฝะตะฝะธะตโ€ฆ" #: main/src/ui/add_conversation/conference_details_fragment.vala:169 msgid "Password required to enter room" msgstr "ะขั€ะตะฑัƒะตั‚ัั ะฟะฐั€ะพะปัŒ ะดะปั ะฒั…ะพะดะฐ ะฒ ะบะพะผะฝะฐั‚ัƒ" #: main/src/ui/add_conversation/conference_details_fragment.vala:174 msgid "Banned from joining or creating conference" msgstr "ะ’ะฐะผ ะทะฐะฟั€ะตั‰ะตะฝะพ ะทะฐั…ะพะดะธั‚ัŒ ะฒ ะบะพะฝั„ะตั€ะตะฝั†ะธะธ ะธะปะธ ัะพะทะดะฐะฒะฐั‚ัŒ ะธั…" #: main/src/ui/add_conversation/conference_details_fragment.vala:176 msgid "Room does not exist" msgstr "ะ”ะฐะฝะฝะฐั ะบะพะผะฝะฐั‚ะฐ ะฝะต ััƒั‰ะตัั‚ะฒัƒะตั‚" #: main/src/ui/add_conversation/conference_details_fragment.vala:178 msgid "Not allowed to create room" msgstr "ะ’ะฐะผ ะทะฐะฟั€ะตั‰ะตะฝะฝะพ ัะพะทะดะฐะฒะฐั‚ัŒ ะบะพะผะฝะฐั‚ั‹" #: main/src/ui/add_conversation/conference_details_fragment.vala:180 msgid "Members-only room" msgstr "ะšะพะผะฝะฐั‚ะฐ ั‚ะพะปัŒะบะพ ะดะปั ัƒั‡ะฐัั‚ะฝะธะบะพะฒ" #: main/src/ui/add_conversation/conference_details_fragment.vala:183 msgid "Choose a different nick" msgstr "ะ’ั‹ะฑะตั€ะธั‚ะต ะดั€ัƒะณะพะน ะฝะธะบะฝะตะนะผ" #: main/src/ui/add_conversation/conference_details_fragment.vala:185 msgid "Too many occupants in room" msgstr "ะกะปะธัˆะบะพะผ ะผะฝะพะณะพ ะฟะพัะตั‚ะธั‚ะตะปะตะน ะฒ ะบะพะผะฝะฐั‚ะต" #: main/src/ui/unified_window.vala:227 msgid "Welcome to Dino!" msgstr "ะ”ะพะฑั€ะพ ะฟะพะถะฐะปะพะฒะฐั‚ัŒ ะฒ Dino!" #: main/src/ui/unified_window.vala:228 msgid "Sign in or create an account to get started." msgstr "ะ’ะพะฟะธะนั‚ะต ะธะปะธ ัะพะทะดะฐะฒะฐะนั‚ะต ัƒั‡ะตั‚ะฝัƒัŽ ะทะฐะฟะธััŒ, ั‡ั‚ะพะฑั‹ ะฝะฐั‡ะฐั‚ัŒ." #: main/src/ui/unified_window.vala:229 msgid "Set up account" msgstr "ะะฐัั‚ั€ะพะธั‚ัŒ ัƒั‡ั‘ั‚ะฝัƒัŽ ะทะฐะฟะธััŒ" #: main/src/ui/unified_window.vala:237 msgid "No active accounts" msgstr "ะะตั‚ ะฐะบั‚ะธะฒะฝั‹ั… ะฐะบะบะฐัƒะฝั‚ะพะฒ" #: main/src/ui/unified_window.vala:238 msgid "Manage accounts" msgstr "ะฃะฟั€ะฐะฒะปะตะฝะธะต ะฐะบะบะฐัƒะฝั‚ะฐะผะธ" #: main/src/ui/unified_window.vala:247 msgid "No active conversations" msgstr "ะะตั‚ ะฐะบั‚ะธะฒะฝั‹ั… ั‡ะฐั‚ะพะฒ" #: main/src/ui/conversation_summary/file_widget.vala:275 #, c-format msgid "Downloading %sโ€ฆ" msgstr "ะกะบะฐั‡ะธะฒะฐะตั‚ัั %sโ€ฆ" #: main/src/ui/conversation_summary/file_widget.vala:281 #, c-format msgid "%s offered: %s" msgstr "%s ะฟั€ะตะดะปะพะถะธะป: %s" #: main/src/ui/conversation_summary/file_widget.vala:283 #, c-format msgid "File offered: %s" msgstr "ะŸั€ะตะดะปะพะถะตะฝ ั„ะฐะนะป: %s" #: main/src/ui/conversation_summary/file_widget.vala:285 msgid "File offered" msgstr "ะŸั€ะตะดะปะพะถะตะฝ ั„ะฐะนะป" #: main/src/ui/conversation_summary/file_widget.vala:290 msgid "File transfer failed" msgstr "ะžัˆะธะฑะบะฐ ะฟะตั€ะตะดะฐั‡ะธ ั„ะฐะนะปะฐ" #: main/src/ui/conversation_summary/date_separator_populator.vala:85 msgid "Today" msgstr "ะกะตะณะพะดะฝั" #: main/src/ui/conversation_summary/date_separator_populator.vala:98 msgid "%a, %b %d" msgstr "%a, %b %d" #: main/src/ui/conversation_summary/chat_state_populator.vala:145 #, c-format msgid "%s, %s and %i others" msgstr "%s, %s ะธ ะตั‰ั‘ %i ั‡ะตะปะพะฒะตะบ" #: main/src/ui/conversation_summary/chat_state_populator.vala:147 #, c-format msgid "%s, %s and %s" msgstr "%s, %s ะธ %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:149 #, c-format msgid "%s and %s" msgstr "%s ะธ %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:154 msgid "is typingโ€ฆ" msgid_plural "are typingโ€ฆ" msgstr[0] "ะฟะตั‡ะฐั‚ะฐะตั‚โ€ฆ" msgstr[1] "ะฟะตั‡ะฐั‚ะฐัŽั‚โ€ฆ" msgstr[2] "ะฟะตั‡ะฐั‚ะฐัŽั‚โ€ฆ" #: main/src/ui/conversation_summary/chat_state_populator.vala:156 msgid "has stopped typing" msgstr "ะฟะตั€ะตัั‚ะฐะป ะฟะตั‡ะฐั‚ะฐั‚ัŒ" #: main/src/ui/conversation_summary/content_item_widget_factory.vala:59 msgid "Message too long" msgstr "ะกะพะพะฑั‰ะตะฝะธะต ัะปะธัˆะบะพะผ ะดะปะธะฝะฝะพะต" #: main/src/ui/conversation_summary/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "ะญั‚ะพั‚ ะบะพะฝั‚ะฐะบั‚ ั…ะพั‡ะตั‚ ะดะพะฑะฐะฒะธั‚ัŒ ะฒะฐั ะฒ ัะฟะธัะพะบ ะบะพะฝั‚ะฐะบั‚ะพะฒ" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:193 #, no-c-format msgid "%x, %Hโˆถ%M" msgstr "%x, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:194 #, no-c-format msgid "%x, %lโˆถ%M %p" msgstr "%x, %lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:197 #, no-c-format msgid "%b %d, %Hโˆถ%M" msgstr "%b %d, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:198 #, no-c-format msgid "%b %d, %lโˆถ%M %p" msgstr "%b %d, %lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:201 #, no-c-format msgid "%a, %Hโˆถ%M" msgstr "%a, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:202 #, no-c-format msgid "%a, %lโˆถ%M %p" msgstr "%a, %lโˆถ%M %p" #: main/src/ui/occupant_menu/list.vala:102 msgid "Owner" msgstr "ะ’ะปะฐะดะตะปะตั†" #: main/src/ui/occupant_menu/list.vala:104 msgid "Admin" msgstr "ะะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€" #: main/src/ui/occupant_menu/list.vala:106 msgid "Member" msgstr "ะฃั‡ะฐัั‚ะฝะธะบ" #: main/src/ui/occupant_menu/list.vala:108 msgid "User" msgstr "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ" #: main/src/ui/occupant_menu/view.vala:29 #: main/src/ui/occupant_menu/view.vala:38 msgid "Invite" msgstr "ะŸั€ะธะณะปะฐัะธั‚ัŒ" #: main/src/ui/occupant_menu/view.vala:37 msgid "Invite to Conference" msgstr "ะŸั€ะธะณะปะฐัะธั‚ัŒ ะฒ ะบะพะฝั„ะตั€ะตะฝั†ะธัŽ" #: main/src/ui/occupant_menu/view.vala:88 msgid "Start private conversation" msgstr "ะะฐั‡ะฐั‚ัŒ ะปะธั‡ะฝั‹ะน ั‡ะฐั‚" #: main/src/ui/occupant_menu/view.vala:96 msgid "Kick" msgstr "ะ’ั‹ะณะฝะฐั‚ัŒ" #: main/src/ui/global_search.vala:140 #, c-format msgid "%i search results" msgstr "ะ ะตะทัƒะปัŒั‚ะฐั‚ั‹ ะฟะพะธัะบะฐ ะฟะพ ะทะฐะฟั€ะพััƒ ยซ%iยป" #: main/src/ui/global_search.vala:167 #, c-format msgid "In %s" msgstr "ะ’ %s" #: main/src/ui/global_search.vala:167 #, c-format msgid "With %s" msgstr "ะก %s" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "ะ˜ะฝั„ะพั€ะผะฐั†ะธั ะพ ะบะพะฝั„ะตั€ะตะฝั†ะธะธ" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:24 msgid "Contact Details" msgstr "ะ˜ะฝั„ะพั€ะผะฐั†ะธั ะพ ะบะพะฝั‚ะฐะบั‚ะต" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "ะะฐะทะฒะฐะฝะธะต ะบะพะผะฝะฐั‚ั‹" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "ะžะฟะธัะฐะฝะธะต ะบะพะผะฝะฐั‚ั‹" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "ะกะพั…ั€ะฐะฝะตะฝะธะต" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "ะšะพะผะฝะฐั‚ะฐ ัะพั…ั€ะฐะฝะธั‚ัั, ะดะฐะถะต ะตัะปะธ ัƒะนะดั‘ั‚ ะฟะพัะปะตะดะฝะธะน ะฟะพัะตั‚ะธั‚ะตะปัŒ" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "ะ”ะพัั‚ัƒะฟะฝะพัั‚ัŒ ะดะปั ะฟัƒะฑะปะธั‡ะฝะพะณะพ ะฟะพะธัะบะฐ" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "ะŸะพัะตั‚ะธั‚ะตะปะธ ะผะพะณัƒั‚ ะผะตะฝัั‚ัŒ ั‚ะตะผัƒ" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Discover real JIDs" msgstr "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะฝะฐัั‚ะพัั‰ะธะต JID" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who may discover real JIDs?" msgstr "ะšั‚ะพ ะผะพะถะตั‚ ะฒะธะดะตั‚ัŒ ะฝะฐัั‚ะพัั‰ะธะต JID?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 msgid "Password" msgstr "ะŸะฐั€ะพะปัŒ" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "Password required for room entry, if any" msgstr "ะขั€ะตะฑัƒะตั‚ัั ะฟะฐั€ะพะปัŒ ะดะปั ะฒั…ะพะดะฐ ะฒ ะบะพะผะฝะฐั‚ัƒ, ะตัะปะธ" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "ะœะพะดะตั€ะธั€ัƒะตะผั‹ะน" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "ะขะพะปัŒะบะพ ะฟะพัะตั‚ะธั‚ะตะปะธ ั ะณะพะปะพัะพะผ ะผะพะณัƒั‚ ะพั‚ะฟั€ะฐะฒะปัั‚ัŒ ัะพะพะฑั‰ะตะฝะธั" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "ะขะพะปัŒะบะพ ะดะปั ัƒั‡ะฐัั‚ะฝะธะบะพะฒ" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "ะขะพะปัŒะบะพ ัƒั‡ะฐัั‚ะฝะธะบะธ ะผะพะณัƒั‚ ะทะฐั…ะพะดะธั‚ัŒ ะฒ ะบะพะผะฝะฐั‚ัƒ" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "ะ˜ัั‚ะพั€ะธั ัะพะพะฑั‰ะตะฝะธะน" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "ะšะพะผะฝะฐั‚ะฐ ะฒั‹ะดะฐะปะฐ ะผะฐะบัะธะผะฐะปัŒะฝะพะต ะบะพะปะธั‡ะตัั‚ะฒะพ ะปะพะณะพะฒ" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "ะšะพะฝั„ะธะณัƒั€ะฐั†ะธั ะบะพะผะฝะฐั‚ั‹" #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/src/ui/contact_details/settings_provider.vala:12 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "ะะฐัั‚ั€ะพะนะบะธ" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "ะ—ะฐะฑะปะพะบะธั€ะพะฒะฐั‚ัŒ" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "ะŸะตั€ะตะฟะธัะบะฐ ะธ ะพะฑะฝะพะฒะปะตะฝะธั ัั‚ะฐั‚ัƒัะพะฒ ะทะฐะฑะปะพะบะธั€ะพะฒะฐะฝั‹ ะฒ ะพะฑะฐ ะฝะฐะฟั€ะฐะฒะปะตะฝะธั" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "ะ›ะพะบะฐะปัŒะฝั‹ะต ะฝะฐัั‚ั€ะพะนะบะธ" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "ะžั‚ะฟั€ะฐะฒะปัั‚ัŒ ัƒะฒะตะดะพะผะปะตะฝะธั ะพ ะฝะฐะฑะพั€ะต ัะพะพะฑั‰ะตะฝะธั" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "ะžั‚ะฟั€ะฐะฒะปัั‚ัŒ ัƒะฒะตะดะพะผะปะตะฝะธั ะพ ะฟั€ะพั‡ั‚ะตะฝะธะธ" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "ะฃะฒะตะดะพะผะปะตะฝะธั" #: main/src/ui/contact_details/settings_provider.vala:82 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:124 msgid "On" msgstr "ะ’ะšะ›" #: main/src/ui/contact_details/settings_provider.vala:84 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "Off" msgstr "ะ’ะซะšะ›" #: main/src/ui/contact_details/settings_provider.vala:86 msgid "Only when mentioned" msgstr "ะขะพะปัŒะบะพ ะฟั€ะธ ัƒะฟะพะผะธะฝะฐะฝะธะธ" #: main/src/ui/contact_details/settings_provider.vala:88 #: main/src/ui/contact_details/settings_provider.vala:123 #, c-format msgid "Default: %s" msgstr "ะŸะพ ัƒะผะพะปั‡ะฐะฝะธัŽ: %s" #: main/src/ui/conversation_titlebar/search_entry.vala:11 msgid "Search messages" msgstr "ะะฐะนั‚ะธ ัะพะพะฑั‰ะตะฝะธั" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "ะฃั‡ะฐัั‚ะฝะธะบะธ" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "ะฃะฒะตะดะพะผะปัั‚ัŒ ะพ ะฝะพะฒั‹ั… ัะพะพะฑั‰ะตะฝะธัั…" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "ะŸั€ะตะฒั€ะฐั‰ะฐั‚ัŒ ัะผะฐะนะปั‹ ะฒ ัะผะพะดะถะธ" #: main/data/manage_accounts/dialog.ui:9 main/data/menu_app.ui:7 msgid "Accounts" msgstr "ะะบะบะฐัƒะฝั‚ั‹" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "ะ›ะพะบะฐะปัŒะฝั‹ะน ะฟัะตะฒะดะพะฝะธะผ" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "ะะตั‚ ะฝะฐัั‚ั€ะพะตะฝะฝั‹ั… ะฐะบะบะฐัƒะฝั‚ะพะฒ" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "ะ”ะพะฑะฐะฒะธั‚ัŒ ะฐะบะบะฐัƒะฝั‚" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "ะ’ะพะนั‚ะธ" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "ะกะพะทะดะฐั‚ัŒ ะฐะบะบะฐัƒะฝั‚" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "ะะต ัƒะดะฐะปะพััŒ ัƒัั‚ะฐะฝะพะฒะธั‚ัŒ ะทะฐั‰ะธั‰ั‘ะฝะฝะพะต ัะพะตะดะธะฝะตะฝะธะต" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "ะŸั€ะธัะพะตะดะธะฝะธั‚ัŒัั" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "ะ’ั‹ะฑะตั€ะธั‚ะต ะฟัƒะฑะปะธั‡ะฝั‹ะน ัะตั€ะฒะตั€" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "ะ˜ะปะธ ะฒะฒะตะดะธั‚ะต ะฐะดั€ะตั ัะตั€ะฒะตั€ะฐ ัะฐะผะธ" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "ะ’ะพะนั‚ะธ" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "ะ’ั‹ะฑั€ะฐั‚ัŒ ะดั€ัƒะณะพะน ัะตั€ะฒะตั€" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "ะะฐัั‚ั€ะพะนะบะฐ ะทะฐะฒะตั€ัˆะตะฝะฐ!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "ะ—ะฐะบะพะฝั‡ะธั‚ัŒ" #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "ะะต ัˆะธั„ั€ะพะฒะฐั‚ัŒ" #: main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "ะ“ะพั€ัั‡ะธะต ะšะปะฐะฒะธัˆะธ" #: main/data/menu_app.ui:21 msgid "About Dino" msgstr "ะž Dino" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "ะกะพะฒั€ะตะผะตะฝะฝั‹ะน ั‡ะฐั‚-ะบะปะธะตะฝั‚ ะฝะฐ XMPP" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino - ัั‚ะพ ัะพะฒั€ะตะผะตะฝะฝั‹ะน ะบะปะธะตะฝั‚ ะดะปั ั‡ะฐั‚ะพะฒ ั ะพั‚ะบั€ั‹ั‚ั‹ะผ ะธัั…ะพะดะฝั‹ะผ ะบะพะดะพะผ, " "ะฝะฐะฟั€ะฐะฒะปะตะฝะฝั‹ะน ะฝะฐ ะฝะฐะดั‘ะถะฝะพะต ะธ ะฟั€ะธะฒะฐั‚ะฝะพะต ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะต Jabber/XMPP ะฝะฐ " "ะฟะตั€ัะพะฝะฐะปัŒะฝั‹ั… ะบะพะผะฟัŒัŽั‚ะตั€ะฐั…." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "ะžะฝ ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ ัะบะฒะพะทะฝะพะต ัˆะธั„ั€ะพะฒะฐะฝะธะต ั‡ะตั€ะตะท OMEMO ะธ OpenPGP ะธ ะฟะพะทะฒะพะปัะตั‚ " "ะฝะฐัั‚ั€ะฐะธะฒะฐั‚ัŒ ั‚ะฐะบะธะต ั„ัƒะฝะบั†ะธะธ, ะบะฐะบ ัƒะฒะตะดะพะผะปะตะฝะธั ะพ ะฟั€ะพั‡ั‚ะตะฝะธะธ ะธ ะฝะฐะฑะพั€ะต ัะพะพะฑั‰ะตะฝะธะน." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino ะทะฐะณั€ัƒะถะฐะตั‚ ะธัั‚ะพั€ะธัŽ ั ัะตั€ะฒะตั€ะฐ ะธ ัะธะฝั…ั€ะพะฝะธะทะธั€ัƒะตั‚ ัะพะพะฑั‰ะตะฝะธั ั ะดั€ัƒะณะธะผะธ " "ัƒัั‚ั€ะพะนัั‚ะฒะฐะผะธ." #: main/data/im.dino.Dino.appdata.xml.in:16 msgid "Main window with conversations" msgstr "ะ“ะปะฐะฒะฝะพะต ะพะบะฝะพ ั ั‡ะฐั‚ะฐะผะธ" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "ะะบะบะฐัƒะฝั‚" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "ะะธะบะฝะตะนะผ" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "ะŸัะตะฒะดะพะฝะธะผ" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "ะ”ะพะฑะฐะฒะธั‚ัŒ ะบะพะฝั‚ะฐะบั‚" #: main/data/shortcuts.ui:12 msgid "General" msgstr "ะžัะฝะพะฒะฝะพะต" #: main/data/shortcuts.ui:32 msgid "Navigation" msgstr "ะะฐะฒะธะณะฐั†ะธั" #: main/data/shortcuts.ui:37 msgid "Jump to next conversation" msgstr "" #: main/data/shortcuts.ui:44 msgid "Jump to previous conversation" msgstr "" #: main/data/global_search.ui:37 msgid "No active search" msgstr "ะะตั‚ ะฐะบั‚ะธะฒะฝะพะณะพ ะฟะพะธัะบะฐ" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "ะ’ะฒะตะดะธั‚ะต ั‚ะตะบัั‚, ั‡ั‚ะพะฑั‹ ะฝะฐั‡ะฐั‚ัŒ ะฟะพะธัะบ" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "ะะตั‚ ะฟะพะดั…ะพะดัั‰ะธั… ัะพะพะฑั‰ะตะฝะธะน" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "ะŸั€ะพะฒะตั€ัŒั‚ะต ัะปะพะฒะฐั€ะธ ะธะปะธ ะฟะพะฟั€ะพะฑัƒะนั‚ะต ะธะทะผะตะฝะธั‚ัŒ ะบั€ะธั‚ะตั€ะธะธ ะฟะพะธัะบะฐ" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "ะะฐะถะผะธั‚ะต ะทะดะตััŒ, ั‡ั‚ะพะฑั‹ ะฝะฐั‡ะฐั‚ัŒ ั€ะฐะทะณะพะฒะพั€ ะธะปะธ ะฟั€ะธัะพะตะดะธะฝะธั‚ัŒัั ะบ ะบะฐะฝะฐะปัƒ." #: main/data/unified_main_content.ui:147 msgid "You have no open chats" msgstr "ะฃ ะฒะฐั ะฝะตั‚ ะพั‚ะบั€ั‹ั‚ั‹ั… ั‡ะฐั‚ะพะฒ" #~ msgid "Failed connecting to %s" #~ msgstr "ะะต ัƒะดะฐะปะพััŒ ัะพะตะดะธะฝะตะฝะธะต ั %s" #~ msgid "Join Conference" #~ msgstr "ะ’ะพะนั‚ะธ ะฒ ะบะพะฝั„ะตั€ะตะฝั†ะธัŽ" #~ msgid "File" #~ msgstr "ะคะฐะนะป" #~ msgid "Preferences" #~ msgstr "ะžะฟั†ะธะธ" #~ msgid "Quit" #~ msgstr "ะ’ั‹ะนั‚ะธ" #~ msgid "JID should be of the form โ€œuser@example.comโ€" #~ msgstr "JID ะดะพะปะถะตะฝ ะฑั‹ั‚ัŒ ะฒ ะฒะธะดะต ยซuser@example.comยป" #~ msgid "Copy Link Address" #~ msgstr "ะšะพะฟะธั€ะพะฒะฐั‚ัŒ ะฐะดั€ะตัั ััั‹ะปะบะธ" #~ msgid "Copy" #~ msgstr "ะšะพะฟะธั€ะพะฒะฐั‚ัŒ" #~ msgid "Select All" #~ msgstr "ะ’ั‹ะฑั€ะฐั‚ัŒ ะฒัั‘" #~ msgid "Search" #~ msgstr "ะŸะพะธัะบ" #~ msgid "Send message marker" #~ msgstr "ะžั‚ะฟั€ะฐะฒะปัั‚ัŒ ะผะฐั€ะบะตั€ั‹ ัะพะพะฑั‰ะตะฝะธะน" #~ msgid "Start Chat" #~ msgstr "ะะฐั‡ะฐั‚ัŒ ั‡ะฐั‚" dino-0.1.0/main/po/sv.po0000644000000000000000000005656113614354364013517 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2019-12-26 23:21+0000\n" "Language-Team: none\n" "Language: sv\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.10\n" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "Image sent" msgstr "Bild skickad" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "File sent" msgstr "Fil skickad" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "Image received" msgstr "Bild mottagen" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "File received" msgstr "Fil mottagen" #: main/src/ui/notifications.vala:98 msgid "Subscription request" msgstr "Prenumerationsfรถrfrรฅgan" #: main/src/ui/notifications.vala:105 main/src/ui/notifications.vala:140 #: main/src/ui/conversation_summary/subscription_notification.vala:37 msgid "Accept" msgstr "Godkรคnn" #: main/src/ui/notifications.vala:106 main/src/ui/notifications.vala:139 #: main/src/ui/conversation_summary/subscription_notification.vala:38 msgid "Deny" msgstr "Neka" #: main/src/ui/notifications.vala:112 #: main/src/ui/manage_accounts/add_account_dialog.vala:258 #: main/src/ui/add_conversation/conference_details_fragment.vala:188 #, c-format msgid "Could not connect to %s" msgstr "Kunde inte ansluta till %s" #: main/src/ui/notifications.vala:128 #, c-format msgid "Invitation to %s" msgstr "Inbjudan till %s" #: main/src/ui/notifications.vala:129 #, c-format msgid "%s invited you to %s" msgstr "%s bjรถd in dig till %s" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "Ta bort konto %s?" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "Ta bort" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "Vรคlj avatar" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "Vรคlj" #: main/src/ui/manage_accounts/dialog.vala:152 main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "Avbryt" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "Bilder" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "Alla filer" #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connectingโ€ฆ" msgstr "Ansluterโ€ฆ" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "Ansluten" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "Frรฅnkopplad" #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "Fel lรถsenord" #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "Ogiltigt TLS-certifikat" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "Fel" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "Lรคgg till konto" #: main/src/ui/manage_accounts/add_account_dialog.vala:166 #, c-format msgid "Sign in to %s" msgstr "Logga in pรฅ %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:210 #, c-format msgid "You can now start using %s" msgstr "Du kan nu bรถrja anvรคnda %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:263 #: main/src/ui/manage_accounts/add_account_dialog.vala:296 #: main/src/ui/manage_accounts/add_account_dialog.vala:306 #: main/src/ui/manage_accounts/add_account_dialog.vala:382 #: main/src/ui/add_conversation/conference_details_fragment.vala:191 msgid "Invalid address" msgstr "Ogiltig adress" #: main/src/ui/manage_accounts/add_account_dialog.vala:278 msgid "Wrong username or password" msgstr "Fel anvรคndarnamn eller lรถsenord" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Something went wrong" msgstr "Nรฅgot gick fel" #: main/src/ui/manage_accounts/add_account_dialog.vala:321 msgid "No response from server" msgstr "Inget svar frรฅn servern" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 #, c-format msgid "Register on %s" msgstr "Registrera konto pรฅ %s" #: main/src/ui/manage_accounts/add_account_dialog.vala:330 msgid "The server requires to sign up through a website" msgstr "Servern krรคver registrering genom en webbsida" #: main/src/ui/manage_accounts/add_account_dialog.vala:332 msgid "Open Registration" msgstr "ร–ppna Registreringen" #: main/src/ui/manage_accounts/add_account_dialog.vala:344 msgid "Register" msgstr "Registrera" #: main/src/ui/manage_accounts/add_account_dialog.vala:346 #, c-format msgid "Check %s for information on how to sign up" msgstr "Kolla %s fรถr information om registrering" #: main/src/ui/conversation_selector/conversation_selector_row.vala:146 #: main/src/ui/conversation_selector/conversation_selector_row.vala:160 #: main/src/ui/util/helper.vala:136 main/src/ui/util/helper.vala:151 msgid "Me" msgstr "Jag" #: main/src/ui/conversation_selector/conversation_selector_row.vala:258 #: main/src/ui/conversation_summary/date_separator_populator.vala:100 #, no-c-format msgid "%b %d" msgstr "%d %b" #: main/src/ui/conversation_selector/conversation_selector_row.vala:262 #: main/src/ui/conversation_summary/date_separator_populator.vala:91 msgid "Yesterday" msgstr "I gรฅr" #: main/src/ui/conversation_selector/conversation_selector_row.vala:265 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:205 #, no-c-format msgid "%Hโˆถ%M" msgstr "%H:%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:266 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:206 #, no-c-format msgid "%lโˆถ%M %p" msgstr "%lโˆถ%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:269 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:210 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i minut sedan" msgstr[1] "%i minuter sedan" #: main/src/ui/conversation_selector/conversation_selector_row.vala:271 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:212 msgid "Just now" msgstr "Nyss" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/unified_window.vala:249 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "Anslut till kanal" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 msgid "Join" msgstr "Anslut" #: main/src/ui/util/helper.vala:126 #, c-format msgid "%s from %s" msgstr "%s frรฅn %s" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:28 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "Lรคgg till" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:42 msgid "Save" msgstr "Spara" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "Nรคsta" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "Tillbaka" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/src/ui/unified_window.vala:248 #: main/data/conversation_list_titlebar_csd.ui:12 #: main/data/conversation_list_titlebar.ui:16 main/data/menu_add.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:20 main/data/shortcuts.ui:17 msgid "Start Conversation" msgstr "Starta Chatt" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "Starta" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joiningโ€ฆ" msgstr "Ansluterโ€ฆ" #: main/src/ui/add_conversation/conference_details_fragment.vala:169 msgid "Password required to enter room" msgstr "Lรถsenord krรคvs fรถr att gรฅ med i rummet" #: main/src/ui/add_conversation/conference_details_fragment.vala:174 msgid "Banned from joining or creating conference" msgstr "Fรถrbjuden att gรฅ med eller skapa gruppchattar" #: main/src/ui/add_conversation/conference_details_fragment.vala:176 msgid "Room does not exist" msgstr "Rummet finns inte" #: main/src/ui/add_conversation/conference_details_fragment.vala:178 msgid "Not allowed to create room" msgstr "Saknar tillรฅtelse att skapa rum" #: main/src/ui/add_conversation/conference_details_fragment.vala:180 msgid "Members-only room" msgstr "Endast medlemmar tillรฅtna i rummet" #: main/src/ui/add_conversation/conference_details_fragment.vala:183 msgid "Choose a different nick" msgstr "Vรคlj ett annat smeknamn" #: main/src/ui/add_conversation/conference_details_fragment.vala:185 msgid "Too many occupants in room" msgstr "Fรถr mรฅnga anvรคndare i rummet" #: main/src/ui/unified_window.vala:227 msgid "Welcome to Dino!" msgstr "Vรคlkommen till Dino!" #: main/src/ui/unified_window.vala:228 msgid "Sign in or create an account to get started." msgstr "Logga in eller skapa ett konto fรถr att komma igรฅng." #: main/src/ui/unified_window.vala:229 msgid "Set up account" msgstr "Stรคll in konto" #: main/src/ui/unified_window.vala:237 msgid "No active accounts" msgstr "Inga aktiva konton" #: main/src/ui/unified_window.vala:238 msgid "Manage accounts" msgstr "Hantera konton" #: main/src/ui/unified_window.vala:247 msgid "No active conversations" msgstr "Inga aktiva chattar" #: main/src/ui/conversation_summary/file_widget.vala:275 #, c-format msgid "Downloading %sโ€ฆ" msgstr "Tar emot %sโ€ฆ" #: main/src/ui/conversation_summary/file_widget.vala:281 #, c-format msgid "%s offered: %s" msgstr "%s erbjรถd: %s" #: main/src/ui/conversation_summary/file_widget.vala:283 #, c-format msgid "File offered: %s" msgstr "Fil erbjuden: %s" #: main/src/ui/conversation_summary/file_widget.vala:285 msgid "File offered" msgstr "Fil erbjuden" #: main/src/ui/conversation_summary/file_widget.vala:290 msgid "File transfer failed" msgstr "Filรถverfรถring misslyckades" #: main/src/ui/conversation_summary/date_separator_populator.vala:85 msgid "Today" msgstr "I dag" #: main/src/ui/conversation_summary/date_separator_populator.vala:98 msgid "%a, %b %d" msgstr "%a %d %b" #: main/src/ui/conversation_summary/chat_state_populator.vala:145 #, c-format msgid "%s, %s and %i others" msgstr "%s, %s och %i andra" #: main/src/ui/conversation_summary/chat_state_populator.vala:147 #, c-format msgid "%s, %s and %s" msgstr "%s, %s och %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:149 #, c-format msgid "%s and %s" msgstr "%s och %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:154 msgid "is typingโ€ฆ" msgid_plural "are typingโ€ฆ" msgstr[0] "skriverโ€ฆ" msgstr[1] "skriverโ€ฆ" #: main/src/ui/conversation_summary/chat_state_populator.vala:156 msgid "has stopped typing" msgstr "har slutat skriva" #: main/src/ui/conversation_summary/content_item_widget_factory.vala:59 msgid "Message too long" msgstr "Meddelandet รคr fรถr lรฅngt" #: main/src/ui/conversation_summary/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "Denna kontakt vill lรคgga till dig i sin kontaktlista" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:193 #, no-c-format msgid "%x, %Hโˆถ%M" msgstr "%x, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:194 #, no-c-format msgid "%x, %lโˆถ%M %p" msgstr "%x, %lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:197 #, no-c-format msgid "%b %d, %Hโˆถ%M" msgstr "%d %b, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:198 #, no-c-format msgid "%b %d, %lโˆถ%M %p" msgstr "%d %b, %lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:201 #, no-c-format msgid "%a, %Hโˆถ%M" msgstr "%a, %Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:202 #, no-c-format msgid "%a, %lโˆถ%M %p" msgstr "%a, %lโˆถ%M %p" #: main/src/ui/occupant_menu/list.vala:102 msgid "Owner" msgstr "ร„gare" #: main/src/ui/occupant_menu/list.vala:104 msgid "Admin" msgstr "Admin" #: main/src/ui/occupant_menu/list.vala:106 msgid "Member" msgstr "Medlem" #: main/src/ui/occupant_menu/list.vala:108 msgid "User" msgstr "Anvรคndare" #: main/src/ui/occupant_menu/view.vala:29 #: main/src/ui/occupant_menu/view.vala:38 msgid "Invite" msgstr "Bjud in" #: main/src/ui/occupant_menu/view.vala:37 msgid "Invite to Conference" msgstr "Bjud in till gruppchatt" #: main/src/ui/occupant_menu/view.vala:88 msgid "Start private conversation" msgstr "Starta privat konversation" #: main/src/ui/occupant_menu/view.vala:96 msgid "Kick" msgstr "Kasta ut" #: main/src/ui/global_search.vala:140 #, c-format msgid "%i search results" msgstr "%i sรถkresultat" #: main/src/ui/global_search.vala:167 #, c-format msgid "In %s" msgstr "I %s" #: main/src/ui/global_search.vala:167 #, c-format msgid "With %s" msgstr "Med %s" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "Konferensdetaljer" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:24 msgid "Contact Details" msgstr "Kontaktdetaljer" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "Namn fรถr rummet" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "Beskrivning fรถr rummet" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "Behรฅll" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "Rummet kommer att finnas kvar รคven efter att sista besรถkaren lรคmnat" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "Offentligt sรถkbart" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "Besรถkare fรฅr รคndra รคmnet" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Discover real JIDs" msgstr "Upptรคckt riktiga JID:n" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who may discover real JIDs?" msgstr "Vem fรฅr upptรคcka riktiga JID:n?" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 msgid "Password" msgstr "Lรถsenord" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "Password required for room entry, if any" msgstr "Lรถsenord som krรคvs fรถr tilltrรคde, om nรฅgot" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "Moderering" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "Endast anvรคndare med tillรฅtelse fรฅr skicka meddelanden" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "Endast fรถr medlemmar" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "Endast medlemmar fรฅr ansluta till rummet" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "Meddelandehistorik" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "Maxgrรคns pรฅ meddelandehistoriken i rummet" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "Rumskonfiguration" #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/src/ui/contact_details/settings_provider.vala:12 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "Instรคllningar" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "Blockera" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "Kommunikation och statusuppdatering i bรฅda riktningar blockeras" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "Lokala instรคllningar" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "Skicka skriftaviseringar" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "Skicka lรคsbekrรคftelser" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "Aviseringar" #: main/src/ui/contact_details/settings_provider.vala:82 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:124 msgid "On" msgstr "Av" #: main/src/ui/contact_details/settings_provider.vala:84 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "Off" msgstr "Pรฅ" #: main/src/ui/contact_details/settings_provider.vala:86 msgid "Only when mentioned" msgstr "Vid omnรคmnande" #: main/src/ui/contact_details/settings_provider.vala:88 #: main/src/ui/contact_details/settings_provider.vala:123 #, c-format msgid "Default: %s" msgstr "Standard: %s" #: main/src/ui/conversation_titlebar/search_entry.vala:11 msgid "Search messages" msgstr "Sรถk meddelanden" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "Medlemmar" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "Avisera nรคr ett nytt meddelande mottages" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "Konvertera smileys till emojin" #: main/data/manage_accounts/dialog.ui:9 main/data/menu_app.ui:7 msgid "Accounts" msgstr "Konton" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "Lokalt alias" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "Inga konfigurerade konton" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "Lรคgg till konto" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "Logga in" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "Skapa konto" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "Kunde inte etablera en sรคker anslutning" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "Anslut" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "Vรคlj en offentlig server" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "Eller ange en serveraddress" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "Logga in i stรคllet" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "Vรคlj en annan server" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "Fรคrdigt!" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "Slutfรถr" #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "Okrypterat" #: main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "Snabbkommandon" #: main/data/menu_app.ui:21 msgid "About Dino" msgstr "Om Dino" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "Modern XMPP-chattklient" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino รคr en moden chattklient fรถr skrivbordet med รถppen kรคllkod. Den erbjuder " "en elegant och pรฅlitligt upplevelse av Jabber/XMPP samtidigt som den ser " "efter din integritet." #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "Dino stรถdjer end-to-end-kryptering med OMEMO och OpenPGP och tillรฅter " "konfigurering av funktioner med integritetspรฅverkan som lรคsbekrรคftelser och " "skriftaviseringar." #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" "Dino hรคmtar historik frรฅn servern och synkroniserar meddelanden med andra " "enheter." #: main/data/im.dino.Dino.appdata.xml.in:16 msgid "Main window with conversations" msgstr "Huvudfรถnster med konversationer" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "Konto" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "Smeknamn" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "Alias" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "Lรคgg till kontakt" #: main/data/shortcuts.ui:12 msgid "General" msgstr "Allmรคnt" #: main/data/shortcuts.ui:32 msgid "Navigation" msgstr "Navigering" #: main/data/shortcuts.ui:37 msgid "Jump to next conversation" msgstr "Hoppa till nรคsta konversation" #: main/data/shortcuts.ui:44 msgid "Jump to previous conversation" msgstr "Hoppa till fรถregรฅende konversation" #: main/data/global_search.ui:37 msgid "No active search" msgstr "Ingen aktiv sรถkning" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "Skriv fรถr att bรถrja sรถka" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "Inga meddelande matchade" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "Kolla stavningen eller ta bort filter" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "Klicka hรคr fรถr att starta en konversation eller gรฅ med i en kanal." #: main/data/unified_main_content.ui:147 msgid "You have no open chats" msgstr "Du har inga รถppna konversationer" #~ msgid "Failed connecting to %s" #~ msgstr "Anslutning till %s misslyckades" #~ msgid "Join Conference" #~ msgstr "Anslut till gruppchatt" #~ msgid "File" #~ msgstr "Fil" dino-0.1.0/main/po/zh_CN.po0000644000000000000000000005654013614354364014065 0ustar rootroot# Chinese translations for PACKAGE package # PACKAGE ่ฝฏไปถๅŒ…็š„็ฎ€ไฝ“ไธญๆ–‡็ฟป่ฏ‘. # Copyright (C) 2017 THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Tong Hui , 2017. msgid "" msgstr "" "Project-Id-Version: dino\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2019-12-21 12:21+0000\n" "Language-Team: Chinese (Simplified) \n" "Language: zh_Hans\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 3.10\n" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "Image sent" msgstr "ๅ›พ็‰‡ๅทฒๅ‘้€" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "File sent" msgstr "ๆ–‡ไปถๅทฒๅ‘้€" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "Image received" msgstr "ๅ›พ็‰‡ๅทฒๆŽฅๆ”ถ" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "File received" msgstr "ๆ–‡ไปถๅทฒๆŽฅๆ”ถ" #: main/src/ui/notifications.vala:98 msgid "Subscription request" msgstr "่ฎข้˜…่ฏทๆฑ‚" #: main/src/ui/notifications.vala:105 main/src/ui/notifications.vala:140 #: main/src/ui/conversation_summary/subscription_notification.vala:37 msgid "Accept" msgstr "ๆŽฅๅ—" #: main/src/ui/notifications.vala:106 main/src/ui/notifications.vala:139 #: main/src/ui/conversation_summary/subscription_notification.vala:38 msgid "Deny" msgstr "ๆ‹’็ป" #: main/src/ui/notifications.vala:112 #: main/src/ui/manage_accounts/add_account_dialog.vala:258 #: main/src/ui/add_conversation/conference_details_fragment.vala:188 #, c-format msgid "Could not connect to %s" msgstr "ๆ— ๆณ•่ฟžๆŽฅๅˆฐ%s" #: main/src/ui/notifications.vala:128 #, c-format msgid "Invitation to %s" msgstr "" #: main/src/ui/notifications.vala:129 #, c-format msgid "%s invited you to %s" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "็งป้™ค่ดฆๆˆท %s๏ผŸ" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "็งป้™ค" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "้€‰ๆ‹ฉๅคดๅƒ" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "้€‰ๆ‹ฉ" #: main/src/ui/manage_accounts/dialog.vala:152 main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "ๅ–ๆถˆ" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "ๅ›พ็‰‡" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "ๆ‰€ๆœ‰ๆ–‡ไปถ" #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connectingโ€ฆ" msgstr "่ฟžๆŽฅไธญโ€ฆ" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "ๅทฒ่ฟžๆŽฅ" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "ๅทฒๆ–ญๅผ€่ฟžๆŽฅ" #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "ๅฏ†็ ้”™่ฏฏ" #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "ๆ— ๆ•ˆ็š„ TLS ่ฏไนฆ" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "้”™่ฏฏ" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "ๆทปๅŠ ๅธๅท" #: main/src/ui/manage_accounts/add_account_dialog.vala:166 #, c-format msgid "Sign in to %s" msgstr "็™ปๅฝ•ๅˆฐ%s" #: main/src/ui/manage_accounts/add_account_dialog.vala:210 #, c-format msgid "You can now start using %s" msgstr "ๆ‚จ็Žฐๅœจๅฏไปฅๅผ€ๅง‹ไฝฟ็”จ%s" #: main/src/ui/manage_accounts/add_account_dialog.vala:263 #: main/src/ui/manage_accounts/add_account_dialog.vala:296 #: main/src/ui/manage_accounts/add_account_dialog.vala:306 #: main/src/ui/manage_accounts/add_account_dialog.vala:382 #: main/src/ui/add_conversation/conference_details_fragment.vala:191 msgid "Invalid address" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:278 msgid "Wrong username or password" msgstr "้”™่ฏฏ็š„็”จๆˆทๅๆˆ–ๅฏ†็ " #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Something went wrong" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:321 msgid "No response from server" msgstr "ๆœๅŠกๅ™จๆœชๅ“ๅบ”" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 #, c-format msgid "Register on %s" msgstr "ๅœจ %s ๆณจๅ†Œ" #: main/src/ui/manage_accounts/add_account_dialog.vala:330 msgid "The server requires to sign up through a website" msgstr "ๆœๅŠกๅ™จ่ฆๆฑ‚ๅœจ็ฝ‘็ซ™ไธŠๆณจๅ†Œ" #: main/src/ui/manage_accounts/add_account_dialog.vala:332 msgid "Open Registration" msgstr "ๅผ€ๆ”พๆณจๅ†Œ" #: main/src/ui/manage_accounts/add_account_dialog.vala:344 msgid "Register" msgstr "ๆณจๅ†Œ" #: main/src/ui/manage_accounts/add_account_dialog.vala:346 #, c-format msgid "Check %s for information on how to sign up" msgstr "ๆฃ€ๆŸฅ %s ไปฅ่Žทๅ–ๅฆ‚ไฝ•ๆณจๅ†Œ็š„ไฟกๆฏ" #: main/src/ui/conversation_selector/conversation_selector_row.vala:146 #: main/src/ui/conversation_selector/conversation_selector_row.vala:160 #: main/src/ui/util/helper.vala:136 main/src/ui/util/helper.vala:151 msgid "Me" msgstr "ๆˆ‘" #: main/src/ui/conversation_selector/conversation_selector_row.vala:258 #: main/src/ui/conversation_summary/date_separator_populator.vala:100 #, no-c-format msgid "%b %d" msgstr "%_m ๆœˆ %_d ๆ—ฅ" #: main/src/ui/conversation_selector/conversation_selector_row.vala:262 #: main/src/ui/conversation_summary/date_separator_populator.vala:91 msgid "Yesterday" msgstr "ๆ˜จๅคฉ" #: main/src/ui/conversation_selector/conversation_selector_row.vala:265 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:205 #, no-c-format msgid "%Hโˆถ%M" msgstr "%Hโˆถ%M" #: main/src/ui/conversation_selector/conversation_selector_row.vala:266 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:206 #, no-c-format msgid "%lโˆถ%M %p" msgstr "%lโˆถ%M %p" #: main/src/ui/conversation_selector/conversation_selector_row.vala:269 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:210 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "%i ๅˆ†้’Ÿไปฅๅ‰" #: main/src/ui/conversation_selector/conversation_selector_row.vala:271 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:212 msgid "Just now" msgstr "ๅˆšๅˆš" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/unified_window.vala:249 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "ๅŠ ๅ…ฅ้ข‘้“" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 msgid "Join" msgstr "ๅŠ ๅ…ฅ" #: main/src/ui/util/helper.vala:126 #, c-format msgid "%s from %s" msgstr "" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:28 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "ๆทปๅŠ " #: main/src/ui/add_conversation/add_groupchat_dialog.vala:42 msgid "Save" msgstr "ไฟๅญ˜" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "ไธ‹ไธ€ไธช" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "่ฟ”ๅ›ž" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/src/ui/unified_window.vala:248 #: main/data/conversation_list_titlebar_csd.ui:12 #: main/data/conversation_list_titlebar.ui:16 main/data/menu_add.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:20 main/data/shortcuts.ui:17 msgid "Start Conversation" msgstr "ๅผ€ๅง‹่Šๅคฉ" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "ๅผ€ๅง‹" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joiningโ€ฆ" msgstr "ๅŠ ๅ…ฅไธญโ€ฆ" #: main/src/ui/add_conversation/conference_details_fragment.vala:169 msgid "Password required to enter room" msgstr "่ฆๅŠ ๅ…ฅๆˆฟ้—ด้œ€่ฆ่พ“ๅ…ฅๅฏ†็ " #: main/src/ui/add_conversation/conference_details_fragment.vala:174 msgid "Banned from joining or creating conference" msgstr "่ขซ็ฆๆญขๅŠ ๅ…ฅๆˆ–ๅˆ›ๅปบ่Šๅคฉๅฎค" #: main/src/ui/add_conversation/conference_details_fragment.vala:176 msgid "Room does not exist" msgstr "ๆˆฟ้—ดไธๅญ˜ๅœจ" #: main/src/ui/add_conversation/conference_details_fragment.vala:178 msgid "Not allowed to create room" msgstr "ไธๅ…่ฎธๅˆ›ๅปบๆˆฟ้—ด" #: main/src/ui/add_conversation/conference_details_fragment.vala:180 msgid "Members-only room" msgstr "ไป…้™ไผšๅ‘˜็š„ๆˆฟ้—ด" #: main/src/ui/add_conversation/conference_details_fragment.vala:183 msgid "Choose a different nick" msgstr "้€‰ๆ‹ฉไธ€ไธชไธๅŒ็š„ๆ˜ต็งฐ" #: main/src/ui/add_conversation/conference_details_fragment.vala:185 msgid "Too many occupants in room" msgstr "ๆˆฟ้—ดๅ†…ไบบๆ•ฐ่ฟ‡ๅคš" #: main/src/ui/unified_window.vala:227 msgid "Welcome to Dino!" msgstr "ๆฌข่ฟŽๆฅๅˆฐ Dino๏ผ" #: main/src/ui/unified_window.vala:228 msgid "Sign in or create an account to get started." msgstr "" #: main/src/ui/unified_window.vala:229 msgid "Set up account" msgstr "่ฎพ็ฝฎๅธๆˆท" #: main/src/ui/unified_window.vala:237 msgid "No active accounts" msgstr "ๆฒกๆœ‰ๆฟ€ๆดป็š„ๅธๅท" #: main/src/ui/unified_window.vala:238 msgid "Manage accounts" msgstr "็ฎก็†ๅธๅท" #: main/src/ui/unified_window.vala:247 msgid "No active conversations" msgstr "ๆฒกๆœ‰ๆฟ€ๆดป็š„ไผš่ฏ" #: main/src/ui/conversation_summary/file_widget.vala:275 #, c-format msgid "Downloading %sโ€ฆ" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:281 #, c-format msgid "%s offered: %s" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:283 #, c-format msgid "File offered: %s" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:285 msgid "File offered" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:290 msgid "File transfer failed" msgstr "" #: main/src/ui/conversation_summary/date_separator_populator.vala:85 msgid "Today" msgstr "ไปŠๅคฉ" #: main/src/ui/conversation_summary/date_separator_populator.vala:98 msgid "%a, %b %d" msgstr "%a๏ผŒ%b%dๆ—ฅ" #: main/src/ui/conversation_summary/chat_state_populator.vala:145 #, c-format msgid "%s, %s and %i others" msgstr "%sใ€%s ไปฅๅŠ %i ไธชๅ…ถไป–ไบบ" #: main/src/ui/conversation_summary/chat_state_populator.vala:147 #, c-format msgid "%s, %s and %s" msgstr "%sใ€%s ๅ’Œ %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:149 #, c-format msgid "%s and %s" msgstr "%s ๅ’Œ %s" #: main/src/ui/conversation_summary/chat_state_populator.vala:154 msgid "is typingโ€ฆ" msgid_plural "are typingโ€ฆ" msgstr[0] "ๆญฃๅœจ่พ“ๅ…ฅโ€ฆ" #: main/src/ui/conversation_summary/chat_state_populator.vala:156 msgid "has stopped typing" msgstr "ๅทฒ็ปๅœๆญข่พ“ๅ…ฅ" #: main/src/ui/conversation_summary/content_item_widget_factory.vala:59 msgid "Message too long" msgstr "ๆถˆๆฏๅคช้•ฟ" #: main/src/ui/conversation_summary/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "่ฏฅ่”็ณปไบบๆƒณๆŠŠไฝ ๅŠ ๅˆฐไป–ไปฌ็š„่”็ณปไบบๅˆ—่กจ" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:193 #, no-c-format msgid "%x, %Hโˆถ%M" msgstr "%x๏ผŒ%Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:194 #, no-c-format msgid "%x, %lโˆถ%M %p" msgstr "%x๏ผŒ%lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:197 #, no-c-format msgid "%b %d, %Hโˆถ%M" msgstr "%_m ๆœˆ %_d ๆ—ฅ๏ผŒ%Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:198 #, no-c-format msgid "%b %d, %lโˆถ%M %p" msgstr "%_m ๆœˆ %_d ๆ—ฅ๏ผŒ%lโˆถ%M %p" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:201 #, no-c-format msgid "%a, %Hโˆถ%M" msgstr "%A๏ผŒ%Hโˆถ%M" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:202 #, no-c-format msgid "%a, %lโˆถ%M %p" msgstr "%A๏ผŒ%lโˆถ%M %p" #: main/src/ui/occupant_menu/list.vala:102 msgid "Owner" msgstr "ๆ‰€ๆœ‰่€…" #: main/src/ui/occupant_menu/list.vala:104 msgid "Admin" msgstr "็ฎก็†ๅ‘˜" #: main/src/ui/occupant_menu/list.vala:106 msgid "Member" msgstr "ๆˆๅ‘˜" #: main/src/ui/occupant_menu/list.vala:108 msgid "User" msgstr "็”จๆˆท" #: main/src/ui/occupant_menu/view.vala:29 #: main/src/ui/occupant_menu/view.vala:38 msgid "Invite" msgstr "้‚€่ฏท" #: main/src/ui/occupant_menu/view.vala:37 msgid "Invite to Conference" msgstr "้‚€่ฏทๅˆฐ่Šๅคฉๅฎค้‡Œ" #: main/src/ui/occupant_menu/view.vala:88 msgid "Start private conversation" msgstr "ๅฏๅŠจ็งๅฏ†ไผš่ฏ" #: main/src/ui/occupant_menu/view.vala:96 msgid "Kick" msgstr "่ธขๅ‡บ" #: main/src/ui/global_search.vala:140 #, c-format msgid "%i search results" msgstr "%i ๆœ็ดข็ป“ๆžœ" #: main/src/ui/global_search.vala:167 #, c-format msgid "In %s" msgstr "ๅœจ%sไธญ" #: main/src/ui/global_search.vala:167 #, c-format msgid "With %s" msgstr "็”จ%s" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "่Šๅคฉๅฎค่ฏฆๆƒ…" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:24 msgid "Contact Details" msgstr "่”็ณปไบบ่ฏฆๆƒ…" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "ๆˆฟ้—ดๅ็งฐ" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "ๆˆฟ้—ด็š„ๆ่ฟฐ" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "ไธ€็›ดๅœจ็บฟ" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "ๆญคๆˆฟ้—ดๅฐ†ไธ€็›ดไฟๆŒๅญ˜ๅœจ็›ดๅˆฐๆœ€ๅŽไธ€ไธชไบบ็ฆปๅผ€" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "ๅ…ฌๅผ€ๅฏๆœ็ดข" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "ๅ‚ไธŽ่€…ๅฏไปฅๆ›ดๆ”นไธป้ข˜" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Discover real JIDs" msgstr "ๅ‘็Žฐ็œŸๅฎž JID" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who may discover real JIDs?" msgstr "่ฐๅฏ่ƒฝๅ‘็Žฐ็œŸๅฎž JID๏ผŸ" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 msgid "Password" msgstr "ๅฏ†็ " #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "Password required for room entry, if any" msgstr "ๅฆ‚ๆžœๆœ‰็š„่ฏ้œ€่ฆๅฏ†็ ๆ‰่ƒฝ่ฟ›ๅ…ฅๆˆฟ้—ด" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "ไธปๆŒ" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "ๅชๆœ‰ๆœ‰ๅฃฐ้Ÿณ็š„ๅ‚ไธŽ่€…ๅฏไปฅๅ‘้€ๆถˆๆฏ" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "ไป…้™ๆˆๅ‘˜" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "ๅชๆœ‰ๆˆๅ‘˜ๆ‰ๅฏไปฅ่ฟ›ๅ…ฅๆˆฟ้—ด" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "ๆถˆๆฏๅކๅฒ่ฎฐๅฝ•" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "่ฟ”ๅ›ž็š„ๆˆฟ้—ด็š„ๅކๅฒๆถˆๆฏ็š„ๆœ€ๅคงๆ•ฐ็›ฎ" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "ๆˆฟ้—ด้…็ฝฎ" #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/src/ui/contact_details/settings_provider.vala:12 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "่ฎพ็ฝฎ" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "ๅฐ็ฆ" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "ๅŒๅ‘้€š่ฎฏๅ’Œ็Šถๆ€ๆ›ดๆ–ฐๅทฒ็ป่ขซๅฐ็ฆ" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "ๆœฌๅœฐ่ฎพ็ฝฎ" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "ๅ‘้€ๆ‰“ๅญ—้€š็Ÿฅ" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "ๅ‘้€ๅทฒ่ฏปๅ›žๆ‰ง" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "้€š็Ÿฅ" #: main/src/ui/contact_details/settings_provider.vala:82 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:124 msgid "On" msgstr "ๅผ€ๅฏ" #: main/src/ui/contact_details/settings_provider.vala:84 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "Off" msgstr "ๅ…ณ้—ญ" #: main/src/ui/contact_details/settings_provider.vala:86 msgid "Only when mentioned" msgstr "ๅชๆœ‰่ขซๆๅˆฐๆ—ถ" #: main/src/ui/contact_details/settings_provider.vala:88 #: main/src/ui/contact_details/settings_provider.vala:123 #, c-format msgid "Default: %s" msgstr "้ป˜่ฎค๏ผš%s" #: main/src/ui/conversation_titlebar/search_entry.vala:11 msgid "Search messages" msgstr "ๆœ็ดขๆถˆๆฏ" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "ๆˆๅ‘˜" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "ๆ–ฐๆถˆๆฏๅˆฐ่พพๆ—ถ้€š็Ÿฅ" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "ๅฐ†็ฌ‘่„ธ่ฝฌๆขๆˆ Emoji" #: main/data/manage_accounts/dialog.ui:9 main/data/menu_app.ui:7 msgid "Accounts" msgstr "ๅธๅท" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "ๆœฌๅœฐๅˆซๅ" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "ๆฒกๆœ‰้…็ฝฎ่ดฆๆˆท" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "ๆทปๅŠ ๆ–ฐ่ดฆๅท" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "็™ปๅฝ•" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "ๅˆ›ๅปบ่ดฆๆˆท" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "ๆ— ๆณ•ๅปบ็ซ‹ๅฎ‰ๅ…จ่ฟžๆŽฅ" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "่ฟžๆŽฅ" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "้€‰ๆ‹ฉไธ€ไธชๅ…ฌๅ…ฑๆœๅŠกๅ™จ" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "ๆˆ–ๆŒ‡ๅฎšไธ€ไธชๆœๅŠกๅ™จๅœฐๅ€" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "ไปฃๆ›ฟ็™ปๅฝ•" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "้€‰ๆ‹ฉๅฆๅค–ไธ€ไธชๆœๅŠกๅ™จ" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "้ƒฝๅ‡†ๅค‡ๅฅฝไบ†๏ผ" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "ๅฎŒๆˆ" #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "ๆœชๅŠ ๅฏ†" #: main/data/menu_app.ui:17 msgid "Keyboard Shortcuts" msgstr "้”ฎ็›˜ๅฟซๆท้”ฎ" #: main/data/menu_app.ui:21 msgid "About Dino" msgstr "ๅ…ณไบŽ Dino" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "็Žฐไปฃ XMPP ่Šๅคฉๅฎขๆˆท็ซฏ" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" "Dino ๆ˜ฏไธ€ไธช็Žฐไปฃ็š„ๅผ€ๆบ่ŠๅคฉๆกŒ้ขๅฎขๆˆท็ซฏใ€‚ๅฎƒ่‡ดๅŠ›ไบŽๆไพ›ไธ€ไธชๆธ…็ˆฝๅˆๅฏ้ ็š„ Jabber/" "XMPP ไฝ“้ชŒ๏ผŒๅŒๆ—ถๅˆไฟๆŠคๆ‚จ็š„้š็งใ€‚" #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" "ๅฎƒๆ”ฏๆŒ OMEMO ๅ’Œ OpenPGP ็ซฏๅฏน็ซฏๅŠ ๅฏ†ๅนถๅ…่ฎธ้…็ฝฎ้š็ง็›ธๅ…ณ็š„็‰นๆ€งๆฏ”ๅฆ‚ๅทฒ่ฏปๅ›žๆ‰งๅ’Œ่พ“ๅ…ฅ" "ๆ้†’ใ€‚" #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "Dino ไปŽๆœๅŠกๅ™จ่Žทๅ–ๆถˆๆฏๅนถๅ’Œๅ…ถไป–่ฎพๅค‡ๅŒๆญฅใ€‚" #: main/data/im.dino.Dino.appdata.xml.in:16 msgid "Main window with conversations" msgstr "ๅธฆๆœ‰ๅฏน่ฏ็š„ไธป็ช—ๅฃ" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "่ดฆๅท" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "ๆ˜ต็งฐ" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "ๅˆซๅ" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "ๅขžๅŠ ่”็ณปไบบ" #: main/data/shortcuts.ui:12 msgid "General" msgstr "ๅธธ่ง„" #: main/data/shortcuts.ui:32 msgid "Navigation" msgstr "ๅฏผ่ˆช" #: main/data/shortcuts.ui:37 msgid "Jump to next conversation" msgstr "" #: main/data/shortcuts.ui:44 msgid "Jump to previous conversation" msgstr "" #: main/data/global_search.ui:37 msgid "No active search" msgstr "ๆ— ๆดปๅŠจ็š„ๆœ็ดข" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "่พ“ๅ…ฅไปฅๅผ€ๅง‹ๆœ็ดข" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "ๆ— ๅŒน้…็š„ๆถˆๆฏ" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "ๆฃ€ๆŸฅๆ‹ผๅ†™ๆˆ–ๅฐ่ฏ•็งป้™ค่ฟ‡ๆปคๅ™จ" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "" #: main/data/unified_main_content.ui:147 msgid "You have no open chats" msgstr "" #~ msgid "Failed connecting to %s" #~ msgstr "่ฟžๆŽฅ %s ๅคฑ่ดฅ" #~ msgid "Join Conference" #~ msgstr "ๅŠ ๅ…ฅ็พค่Š" #~ msgid "File" #~ msgstr "ๆ–‡ไปถ" #~ msgid "Communicate happiness." #~ msgstr "ๆฒŸ้€šๅฟซไนใ€‚" #~ msgid "Preferences" #~ msgstr "้€‰้กน" #~ msgid "Quit" #~ msgstr "้€€ๅ‡บ" #~ msgid "JID should be of the form โ€œuser@example.comโ€" #~ msgstr "JID ๅฟ…้กปๅฝขๅฆ‚ โ€œuser@example.comโ€" #~ msgid "Copy Link Address" #~ msgstr "ๅคๅˆถ้“พๆŽฅๅœฐๅ€" #~ msgid "Copy" #~ msgstr "ๅคๅˆถ" #~ msgid "Select All" #~ msgstr "ๅ…จ้€‰" #~ msgid "Search" #~ msgstr "ๆœ็ดข" #~ msgid "Send message marker" #~ msgstr "ๅ‘้€ๆถˆๆฏๆ ‡่ฎฐ" #~ msgid "Start Chat" #~ msgstr "ๅผ€ๅง‹่Šๅคฉ" #~ msgid "Request presence updates" #~ msgstr "่ฏทๆฑ‚ๅœจ็บฟๆ›ดๆ–ฐ" #~ msgid "Join on startup" #~ msgstr "ๅฏๅŠจๆ—ถๅŠ ๅ…ฅ" #~ msgid "Add Chat" #~ msgstr "ๆทปๅŠ ่Šๅคฉ" dino-0.1.0/main/po/zh_TW.po0000644000000000000000000004754113614354364014120 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2019-12-21 12:21+0000\n" "Language-Team: none\n" "Language: zh_Hant\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 3.10\n" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "Image sent" msgstr "" #: main/src/ui/notifications.vala:66 #: main/src/ui/conversation_selector/conversation_selector_row.vala:166 msgid "File sent" msgstr "" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "Image received" msgstr "" #: main/src/ui/notifications.vala:68 #: main/src/ui/conversation_selector/conversation_selector_row.vala:168 msgid "File received" msgstr "" #: main/src/ui/notifications.vala:98 msgid "Subscription request" msgstr "" #: main/src/ui/notifications.vala:105 main/src/ui/notifications.vala:140 #: main/src/ui/conversation_summary/subscription_notification.vala:37 msgid "Accept" msgstr "" #: main/src/ui/notifications.vala:106 main/src/ui/notifications.vala:139 #: main/src/ui/conversation_summary/subscription_notification.vala:38 msgid "Deny" msgstr "" #: main/src/ui/notifications.vala:112 #: main/src/ui/manage_accounts/add_account_dialog.vala:258 #: main/src/ui/add_conversation/conference_details_fragment.vala:188 #, c-format msgid "Could not connect to %s" msgstr "" #: main/src/ui/notifications.vala:128 #, c-format msgid "Invitation to %s" msgstr "" #: main/src/ui/notifications.vala:129 #, c-format msgid "%s invited you to %s" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:125 #, c-format msgid "Remove account %s?" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:128 msgid "Remove" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select avatar" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:152 msgid "Select" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:152 main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:15 #: main/src/ui/add_conversation/add_conference_dialog.vala:122 #: main/src/ui/add_conversation/select_contact_dialog.vala:38 #: main/data/add_conversation/add_groupchat_dialog.ui:11 #: main/data/add_conversation/add_contact_dialog.ui:12 msgid "Cancel" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:159 msgid "Images" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:163 msgid "All files" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:219 msgid "Connectingโ€ฆ" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:221 msgid "Connected" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:223 msgid "Disconnected" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:232 msgid "Wrong password" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:234 msgid "Invalid TLS certificate" msgstr "" #: main/src/ui/manage_accounts/dialog.vala:237 #: main/src/ui/manage_accounts/dialog.vala:239 msgid "Error" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:82 msgid "Add Account" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:166 #, c-format msgid "Sign in to %s" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:210 #, c-format msgid "You can now start using %s" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:263 #: main/src/ui/manage_accounts/add_account_dialog.vala:296 #: main/src/ui/manage_accounts/add_account_dialog.vala:306 #: main/src/ui/manage_accounts/add_account_dialog.vala:382 #: main/src/ui/add_conversation/conference_details_fragment.vala:191 msgid "Invalid address" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:278 msgid "Wrong username or password" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:281 msgid "Something went wrong" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:321 msgid "No response from server" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:327 #, c-format msgid "Register on %s" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:330 msgid "The server requires to sign up through a website" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:332 msgid "Open Registration" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:344 msgid "Register" msgstr "" #: main/src/ui/manage_accounts/add_account_dialog.vala:346 #, c-format msgid "Check %s for information on how to sign up" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:146 #: main/src/ui/conversation_selector/conversation_selector_row.vala:160 #: main/src/ui/util/helper.vala:136 main/src/ui/util/helper.vala:151 msgid "Me" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:258 #: main/src/ui/conversation_summary/date_separator_populator.vala:100 #, no-c-format msgid "%b %d" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:262 #: main/src/ui/conversation_summary/date_separator_populator.vala:91 msgid "Yesterday" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:265 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:205 #, no-c-format msgid "%Hโˆถ%M" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:266 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:206 #, no-c-format msgid "%lโˆถ%M %p" msgstr "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:269 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:210 #, c-format msgid "%i min ago" msgid_plural "%i mins ago" msgstr[0] "" #: main/src/ui/conversation_selector/conversation_selector_row.vala:271 #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:212 msgid "Just now" msgstr "" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:26 #: main/src/ui/unified_window.vala:249 main/data/menu_add.ui:13 #: main/data/shortcuts.ui:24 msgid "Join Channel" msgstr "" #: main/src/ui/application.vala:192 #: main/src/ui/add_conversation/add_conference_dialog.vala:66 #: main/src/ui/add_conversation/add_conference_dialog.vala:142 #: main/src/ui/add_conversation/conference_details_fragment.vala:157 msgid "Join" msgstr "" #: main/src/ui/util/helper.vala:126 #, c-format msgid "%s from %s" msgstr "" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:28 #: main/data/add_conversation/add_contact_dialog.ui:24 msgid "Add" msgstr "" #: main/src/ui/add_conversation/add_groupchat_dialog.vala:42 msgid "Save" msgstr "" #: main/src/ui/add_conversation/add_conference_dialog.vala:47 #: main/src/ui/add_conversation/add_conference_dialog.vala:118 #: main/data/manage_accounts/add_account_dialog.ui:99 #: main/data/manage_accounts/add_account_dialog.ui:410 #: main/data/manage_accounts/add_account_dialog.ui:490 msgid "Next" msgstr "" #: main/src/ui/add_conversation/add_conference_dialog.vala:147 #: main/data/manage_accounts/add_account_dialog.ui:175 #: main/data/manage_accounts/add_account_dialog.ui:267 msgid "Back" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:97 #: main/src/ui/unified_window.vala:248 #: main/data/conversation_list_titlebar_csd.ui:12 #: main/data/conversation_list_titlebar.ui:16 main/data/menu_add.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:20 main/data/shortcuts.ui:17 msgid "Start Conversation" msgstr "" #: main/src/ui/add_conversation/select_contact_dialog.vala:98 msgid "Start" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:149 msgid "Joiningโ€ฆ" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:169 msgid "Password required to enter room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:174 msgid "Banned from joining or creating conference" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:176 msgid "Room does not exist" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:178 msgid "Not allowed to create room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:180 msgid "Members-only room" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:183 msgid "Choose a different nick" msgstr "" #: main/src/ui/add_conversation/conference_details_fragment.vala:185 msgid "Too many occupants in room" msgstr "" #: main/src/ui/unified_window.vala:227 msgid "Welcome to Dino!" msgstr "" #: main/src/ui/unified_window.vala:228 msgid "Sign in or create an account to get started." msgstr "" #: main/src/ui/unified_window.vala:229 msgid "Set up account" msgstr "" #: main/src/ui/unified_window.vala:237 msgid "No active accounts" msgstr "" #: main/src/ui/unified_window.vala:238 msgid "Manage accounts" msgstr "" #: main/src/ui/unified_window.vala:247 msgid "No active conversations" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:275 #, c-format msgid "Downloading %sโ€ฆ" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:281 #, c-format msgid "%s offered: %s" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:283 #, c-format msgid "File offered: %s" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:285 msgid "File offered" msgstr "" #: main/src/ui/conversation_summary/file_widget.vala:290 msgid "File transfer failed" msgstr "" #: main/src/ui/conversation_summary/date_separator_populator.vala:85 msgid "Today" msgstr "" #: main/src/ui/conversation_summary/date_separator_populator.vala:98 msgid "%a, %b %d" msgstr "" #: main/src/ui/conversation_summary/chat_state_populator.vala:145 #, c-format msgid "%s, %s and %i others" msgstr "" #: main/src/ui/conversation_summary/chat_state_populator.vala:147 #, c-format msgid "%s, %s and %s" msgstr "" #: main/src/ui/conversation_summary/chat_state_populator.vala:149 #, c-format msgid "%s and %s" msgstr "" #: main/src/ui/conversation_summary/chat_state_populator.vala:154 msgid "is typingโ€ฆ" msgid_plural "are typingโ€ฆ" msgstr[0] "" #: main/src/ui/conversation_summary/chat_state_populator.vala:156 msgid "has stopped typing" msgstr "" #: main/src/ui/conversation_summary/content_item_widget_factory.vala:59 msgid "Message too long" msgstr "" #: main/src/ui/conversation_summary/subscription_notification.vala:48 msgid "This contact would like to add you to their contact list" msgstr "" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:193 #, no-c-format msgid "%x, %Hโˆถ%M" msgstr "" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:194 #, no-c-format msgid "%x, %lโˆถ%M %p" msgstr "" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:197 #, no-c-format msgid "%b %d, %Hโˆถ%M" msgstr "" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:198 #, no-c-format msgid "%b %d, %lโˆถ%M %p" msgstr "" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:201 #, no-c-format msgid "%a, %Hโˆถ%M" msgstr "" #: main/src/ui/conversation_summary/conversation_item_skeleton.vala:202 #, no-c-format msgid "%a, %lโˆถ%M %p" msgstr "" #: main/src/ui/occupant_menu/list.vala:102 msgid "Owner" msgstr "" #: main/src/ui/occupant_menu/list.vala:104 msgid "Admin" msgstr "" #: main/src/ui/occupant_menu/list.vala:106 msgid "Member" msgstr "" #: main/src/ui/occupant_menu/list.vala:108 msgid "User" msgstr "" #: main/src/ui/occupant_menu/view.vala:29 #: main/src/ui/occupant_menu/view.vala:38 msgid "Invite" msgstr "" #: main/src/ui/occupant_menu/view.vala:37 msgid "Invite to Conference" msgstr "" #: main/src/ui/occupant_menu/view.vala:88 msgid "Start private conversation" msgstr "" #: main/src/ui/occupant_menu/view.vala:96 msgid "Kick" msgstr "" #: main/src/ui/global_search.vala:140 #, c-format msgid "%i search results" msgstr "" #: main/src/ui/global_search.vala:167 #, c-format msgid "In %s" msgstr "" #: main/src/ui/global_search.vala:167 #, c-format msgid "With %s" msgstr "" #: main/src/ui/contact_details/dialog.vala:37 msgid "Conference Details" msgstr "" #: main/src/ui/contact_details/dialog.vala:37 main/data/menu_conversation.ui:7 #: main/data/im.dino.Dino.appdata.xml.in:24 msgid "Contact Details" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:50 msgid "Name of the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:53 msgid "Description of the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:56 msgid "Persistent" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:57 msgid "The room will persist after the last occupant leaves" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:60 msgid "Publicly searchable" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:63 msgid "Occupants may change the subject" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:66 msgid "Discover real JIDs" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:67 msgid "Who may discover real JIDs?" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:70 #: main/data/manage_accounts/dialog.ui:170 #: main/data/manage_accounts/add_account_dialog.ui:211 #: main/data/add_conversation/conference_details_fragment.ui:175 #: main/data/add_conversation/add_groupchat_dialog.ui:147 msgid "Password" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:71 msgid "Password required for room entry, if any" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:74 msgid "Moderated" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:75 msgid "Only occupants with voice may send messages" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:78 msgid "Members only" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:79 msgid "Only members may enter the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:82 msgid "Message history" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:83 msgid "Maximum amount of backlog issued by the room" msgstr "" #: main/src/ui/contact_details/muc_config_form_provider.vala:89 msgid "Room Configuration" msgstr "" #: main/src/ui/contact_details/blocking_provider.vala:31 #: main/src/ui/contact_details/settings_provider.vala:12 #: main/data/settings_dialog.ui:7 main/data/menu_app.ui:11 msgid "Settings" msgstr "" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Block" msgstr "" #: main/src/ui/contact_details/blocking_provider.vala:31 msgid "Communication and status updates in either direction are blocked" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:13 msgid "Local Settings" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:28 #: main/data/settings_dialog.ui:22 msgid "Send typing notifications" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:33 #: main/data/settings_dialog.ui:34 msgid "Send read receipts" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:38 #: main/src/ui/contact_details/settings_provider.vala:47 msgid "Notifications" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:82 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:124 msgid "On" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:84 #: main/src/ui/contact_details/settings_provider.vala:122 #: main/src/ui/contact_details/settings_provider.vala:125 msgid "Off" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:86 msgid "Only when mentioned" msgstr "" #: main/src/ui/contact_details/settings_provider.vala:88 #: main/src/ui/contact_details/settings_provider.vala:123 #, c-format msgid "Default: %s" msgstr "" #: main/src/ui/conversation_titlebar/search_entry.vala:11 msgid "Search messages" msgstr "" #: main/src/ui/conversation_titlebar/occupants_entry.vala:37 msgid "Members" msgstr "" #: main/data/settings_dialog.ui:46 msgid "Notify when a new message arrives" msgstr "" #: main/data/settings_dialog.ui:58 msgid "Convert smileys to emojis" msgstr "" #: main/data/manage_accounts/dialog.ui:9 main/data/menu_app.ui:7 msgid "Accounts" msgstr "" #: main/data/manage_accounts/dialog.ui:200 msgid "Local alias" msgstr "" #: main/data/manage_accounts/dialog.ui:255 msgid "No accounts configured" msgstr "" #: main/data/manage_accounts/dialog.ui:266 msgid "Add an account" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:31 msgid "Sign in" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:78 #: main/data/manage_accounts/add_account_dialog.ui:328 msgid "Create account" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:153 msgid "Could not establish a secure connection" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:288 msgid "Connect" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:340 msgid "Choose a public server" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:369 msgid "Or specify a server address" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:389 msgid "Sign in instead" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:470 msgid "Pick another server" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:542 msgid "All set up!" msgstr "" #: main/data/manage_accounts/add_account_dialog.ui:578 msgid "Finish" msgstr "" #: main/data/menu_encryption.ui:14 msgid "Unencrypted" msgstr "" #: main/data/menu_app.ui:17 #, fuzzy msgid "Keyboard Shortcuts" msgstr "้ต็›คๆทๅพ‘้ต" #: main/data/menu_app.ui:21 #, fuzzy msgid "About Dino" msgstr "้—œๆ–ผ Dino" #: main/data/im.dino.Dino.appdata.xml.in:8 msgid "Modern XMPP Chat Client" msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:10 msgid "" "Dino is a modern open-source chat client for the desktop. It focuses on " "providing a clean and reliable Jabber/XMPP experience while having your " "privacy in mind." msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:11 msgid "" "It supports end-to-end encryption with OMEMO and OpenPGP and allows " "configuring privacy-related features such as read receipts and typing " "notifications." msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:12 msgid "" "Dino fetches history from the server and synchronizes messages with other " "devices." msgstr "" #: main/data/im.dino.Dino.appdata.xml.in:16 msgid "Main window with conversations" msgstr "" #: main/data/add_conversation/conference_details_fragment.ui:19 #: main/data/add_conversation/add_groupchat_dialog.ui:49 #: main/data/add_conversation/add_contact_dialog.ui:49 msgid "Account" msgstr "" #: main/data/add_conversation/conference_details_fragment.ui:123 #: main/data/add_conversation/add_groupchat_dialog.ui:120 msgid "Nick" msgstr "" #: main/data/add_conversation/add_groupchat_dialog.ui:175 #: main/data/add_conversation/add_contact_dialog.ui:102 msgid "Alias" msgstr "" #: main/data/add_conversation/add_contact_dialog.ui:8 msgid "Add Contact" msgstr "" #: main/data/shortcuts.ui:12 msgid "General" msgstr "ไธ€่ˆฌ" #: main/data/shortcuts.ui:32 msgid "Navigation" msgstr "ๅฐŽ่ˆช" #: main/data/shortcuts.ui:37 msgid "Jump to next conversation" msgstr "" #: main/data/shortcuts.ui:44 msgid "Jump to previous conversation" msgstr "" #: main/data/global_search.ui:37 msgid "No active search" msgstr "" #: main/data/global_search.ui:52 msgid "Type to start a search" msgstr "" #: main/data/global_search.ui:85 msgid "No matching messages" msgstr "" #: main/data/global_search.ui:100 msgid "Check the spelling or try to remove filters" msgstr "" #: main/data/unified_main_content.ui:48 msgid "Click here to start a conversation or join a channel." msgstr "" #: main/data/unified_main_content.ui:147 msgid "You have no open chats" msgstr "" dino-0.1.0/main/src/0000755000000000000000000000000013614354364012663 5ustar rootrootdino-0.1.0/main/src/emojichooser.c0000644000000000000000000006617013614354364015527 0ustar rootroot/* gtkemojichooser.c: An Emoji chooser widget * Copyright 2017, Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "emojichooser.h" #define BOX_SPACE 6 typedef struct { GtkWidget *box; GtkWidget *heading; GtkWidget *button; const char *first; gunichar label; gboolean empty; } EmojiSection; struct _DinoEmojiChooser { GtkPopover parent_instance; GtkWidget *search_entry; GtkWidget *stack; GtkWidget *scrolled_window; int emoji_max_width; EmojiSection recent; EmojiSection people; EmojiSection body; EmojiSection nature; EmojiSection food; EmojiSection travel; EmojiSection activities; EmojiSection objects; EmojiSection symbols; EmojiSection flags; GtkGesture *recent_long_press; GtkGesture *recent_multi_press; GtkGesture *people_long_press; GtkGesture *people_multi_press; GtkGesture *body_long_press; GtkGesture *body_multi_press; GVariant *data; GtkWidget *box; GVariantIter *iter; guint populate_idle; GSettings *settings; }; struct _DinoEmojiChooserClass { GtkPopoverClass parent_class; gboolean (* popover_button_release_event) (GtkWidget *widget, GdkEventButton *event); }; enum { EMOJI_PICKED, LAST_SIGNAL }; static int signals[LAST_SIGNAL]; G_DEFINE_TYPE (DinoEmojiChooser, dino_emoji_chooser, GTK_TYPE_POPOVER) static void dino_emoji_chooser_finalize (GObject *object) { DinoEmojiChooser *chooser = DINO_EMOJI_CHOOSER (object); if (chooser->populate_idle) g_source_remove (chooser->populate_idle); g_variant_unref (chooser->data); g_object_unref (chooser->settings); g_clear_object (&chooser->recent_long_press); g_clear_object (&chooser->recent_multi_press); g_clear_object (&chooser->people_long_press); g_clear_object (&chooser->people_multi_press); g_clear_object (&chooser->body_long_press); g_clear_object (&chooser->body_multi_press); G_OBJECT_CLASS (dino_emoji_chooser_parent_class)->finalize (object); } static void scroll_to_section (GtkButton *button, gpointer data) { EmojiSection *section = data; DinoEmojiChooser *chooser; GtkAdjustment *adj; GtkAllocation alloc = { 0, 0, 0, 0 }; chooser = DINO_EMOJI_CHOOSER (gtk_widget_get_ancestor (GTK_WIDGET (button), GTK_TYPE_EMOJI_CHOOSER)); adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (chooser->scrolled_window)); if (section->heading) gtk_widget_get_allocation (section->heading, &alloc); gtk_adjustment_set_value (adj, alloc.y - BOX_SPACE); } static void add_emoji (GtkWidget *box, gboolean prepend, GVariant *item, gunichar modifier, DinoEmojiChooser *chooser); #define MAX_RECENT (7*3) static void populate_recent_section (DinoEmojiChooser *chooser) { GVariant *variant; GVariant *item; GVariantIter iter; gboolean empty = FALSE; variant = g_settings_get_value (chooser->settings, "recent-emoji"); g_variant_iter_init (&iter, variant); while ((item = g_variant_iter_next_value (&iter))) { GVariant *emoji_data; gunichar modifier; emoji_data = g_variant_get_child_value (item, 0); g_variant_get_child (item, 1, "u", &modifier); add_emoji (chooser->recent.box, FALSE, emoji_data, modifier, chooser); g_variant_unref (emoji_data); g_variant_unref (item); empty = FALSE; } if (!empty) { gtk_widget_show (chooser->recent.box); gtk_widget_set_sensitive (chooser->recent.button, TRUE); } g_variant_unref (variant); } static void add_recent_item (DinoEmojiChooser *chooser, GVariant *item, gunichar modifier) { GList *children, *l; int i; GVariantBuilder builder; g_variant_ref (item); g_variant_builder_init (&builder, G_VARIANT_TYPE ("a((auss)u)")); g_variant_builder_add (&builder, "(@(auss)u)", item, modifier); children = gtk_container_get_children (GTK_CONTAINER (chooser->recent.box)); for (l = children, i = 1; l; l = l->next, i++) { GVariant *item2 = g_object_get_data (G_OBJECT (l->data), "emoji-data"); gunichar modifier2 = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (l->data), "modifier")); if (modifier == modifier2 && g_variant_equal (item, item2)) { gtk_widget_destroy (GTK_WIDGET (l->data)); i--; continue; } if (i >= MAX_RECENT) { gtk_widget_destroy (GTK_WIDGET (l->data)); continue; } g_variant_builder_add (&builder, "(@(auss)u)", item2, modifier2); } g_list_free (children); add_emoji (chooser->recent.box, TRUE, item, modifier, chooser); /* Enable recent */ gtk_widget_show (chooser->recent.box); gtk_widget_set_sensitive (chooser->recent.button, TRUE); g_settings_set_value (chooser->settings, "recent-emoji", g_variant_builder_end (&builder)); g_variant_unref (item); } static void emoji_activated (GtkFlowBox *box, GtkFlowBoxChild *child, gpointer data) { DinoEmojiChooser *chooser = data; char *text; GtkWidget *ebox; GtkWidget *label; GVariant *item; gunichar modifier; gtk_popover_popdown (GTK_POPOVER (chooser)); ebox = gtk_bin_get_child (GTK_BIN (child)); label = gtk_bin_get_child (GTK_BIN (ebox)); text = g_strdup (gtk_label_get_label (GTK_LABEL (label))); item = (GVariant*) g_object_get_data (G_OBJECT (child), "emoji-data"); modifier = (gunichar) GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (child), "modifier")); add_recent_item (chooser, item, modifier); g_signal_emit (data, signals[EMOJI_PICKED], 0, text); g_free (text); } static gboolean has_variations (GVariant *emoji_data) { GVariant *codes; gsize i; gboolean has_variations; has_variations = FALSE; codes = g_variant_get_child_value (emoji_data, 0); for (i = 0; i < g_variant_n_children (codes); i++) { gunichar code; g_variant_get_child (codes, i, "u", &code); if (code == 0) { has_variations = TRUE; break; } } g_variant_unref (codes); return has_variations; } static void show_variations (DinoEmojiChooser *chooser, GtkWidget *child) { GtkWidget *popover; GtkWidget *view; GtkWidget *box; GVariant *emoji_data; GtkWidget *parent_popover; gunichar modifier; if (!child) return; emoji_data = (GVariant*) g_object_get_data (G_OBJECT (child), "emoji-data"); if (!emoji_data) return; if (!has_variations (emoji_data)) return; parent_popover = gtk_widget_get_ancestor (child, GTK_TYPE_POPOVER); popover = gtk_popover_new (child); view = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gtk_style_context_add_class (gtk_widget_get_style_context (view), "view"); box = gtk_flow_box_new (); gtk_flow_box_set_homogeneous (GTK_FLOW_BOX (box), TRUE); gtk_flow_box_set_min_children_per_line (GTK_FLOW_BOX (box), 6); gtk_flow_box_set_max_children_per_line (GTK_FLOW_BOX (box), 6); gtk_flow_box_set_activate_on_single_click (GTK_FLOW_BOX (box), TRUE); gtk_flow_box_set_selection_mode (GTK_FLOW_BOX (box), GTK_SELECTION_NONE); gtk_container_add (GTK_CONTAINER (popover), view); gtk_container_add (GTK_CONTAINER (view), box); g_signal_connect (box, "child-activated", G_CALLBACK (emoji_activated), parent_popover); add_emoji (box, FALSE, emoji_data, 0, chooser); for (modifier = 0x1f3fb; modifier <= 0x1f3ff; modifier++) add_emoji (box, FALSE, emoji_data, modifier, chooser); gtk_widget_show_all (view); gtk_popover_popup (GTK_POPOVER (popover)); } static void update_hover (GtkWidget *widget, GdkEvent *event, gpointer data) { if (event->type == GDK_ENTER_NOTIFY) gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_PRELIGHT, FALSE); else gtk_widget_unset_state_flags (widget, GTK_STATE_FLAG_PRELIGHT); } static void long_pressed_cb (GtkGesture *gesture, double x, double y, gpointer data) { DinoEmojiChooser *chooser = data; GtkWidget *box; GtkWidget *child; box = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)); child = GTK_WIDGET (gtk_flow_box_get_child_at_pos (GTK_FLOW_BOX (box), x, y)); show_variations (chooser, child); } static void pressed_cb (GtkGesture *gesture, int n_press, double x, double y, gpointer data) { DinoEmojiChooser *chooser = data; GtkWidget *box; GtkWidget *child; box = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)); child = GTK_WIDGET (gtk_flow_box_get_child_at_pos (GTK_FLOW_BOX (box), x, y)); show_variations (chooser, child); } static gboolean popup_menu (GtkWidget *widget, gpointer data) { DinoEmojiChooser *chooser = data; show_variations (chooser, widget); return TRUE; } static void add_emoji (GtkWidget *box, gboolean prepend, GVariant *item, gunichar modifier, DinoEmojiChooser *chooser) { GtkWidget *child; GtkWidget *ebox; GtkWidget *label; PangoAttrList *attrs; GVariant *codes; char text[64]; char *p = text; gsize i; PangoLayout *layout; PangoRectangle rect; codes = g_variant_get_child_value (item, 0); for (i = 0; i < g_variant_n_children (codes); i++) { gunichar code; g_variant_get_child (codes, i, "u", &code); if (code == 0) code = modifier; if (code != 0) p += g_unichar_to_utf8 (code, p); } g_variant_unref (codes); p += g_unichar_to_utf8 (0xFE0F, p); /* U+FE0F is the Emoji variation selector */ p[0] = 0; label = gtk_label_new (text); attrs = pango_attr_list_new (); pango_attr_list_insert (attrs, pango_attr_scale_new (PANGO_SCALE_X_LARGE)); gtk_label_set_attributes (GTK_LABEL (label), attrs); pango_attr_list_unref (attrs); layout = gtk_label_get_layout (GTK_LABEL (label)); pango_layout_get_extents (layout, &rect, NULL); /* Check for fallback rendering that generates too wide items */ if (pango_layout_get_unknown_glyphs_count (layout) > 0 || rect.width >= 1.5 * chooser->emoji_max_width) { gtk_widget_destroy (label); return; } child = gtk_flow_box_child_new (); gtk_style_context_add_class (gtk_widget_get_style_context (child), "emoji"); g_object_set_data_full (G_OBJECT (child), "emoji-data", g_variant_ref (item), (GDestroyNotify)g_variant_unref); if (modifier != 0) g_object_set_data (G_OBJECT (child), "modifier", GUINT_TO_POINTER (modifier)); ebox = gtk_event_box_new (); gtk_widget_add_events (ebox, GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK); g_signal_connect (ebox, "enter-notify-event", G_CALLBACK (update_hover), FALSE); g_signal_connect (ebox, "leave-notify-event", G_CALLBACK (update_hover), FALSE); gtk_container_add (GTK_CONTAINER (child), ebox); gtk_container_add (GTK_CONTAINER (ebox), label); gtk_widget_show_all (child); if (chooser) g_signal_connect (child, "popup-menu", G_CALLBACK (popup_menu), chooser); gtk_flow_box_insert (GTK_FLOW_BOX (box), child, prepend ? 0 : -1); } static gboolean populate_emoji_chooser (gpointer data) { DinoEmojiChooser *chooser = data; GBytes *bytes = NULL; GVariant *item; guint64 start, now; start = g_get_monotonic_time (); if (!chooser->data) { bytes = g_resources_lookup_data ("/org/gtk/libgtk/emoji/emoji.data", 0, NULL); chooser->data = g_variant_ref_sink (g_variant_new_from_bytes (G_VARIANT_TYPE ("a(auss)"), bytes, TRUE)); } if (!chooser->iter) { chooser->iter = g_variant_iter_new (chooser->data); chooser->box = chooser->people.box; } while ((item = g_variant_iter_next_value (chooser->iter))) { const char *name; g_variant_get_child (item, 1, "&s", &name); if (g_strcmp0 (name, chooser->body.first) == 0) chooser->box = chooser->body.box; else if (g_strcmp0 (name, chooser->nature.first) == 0) chooser->box = chooser->nature.box; else if (g_strcmp0 (name, chooser->food.first) == 0) chooser->box = chooser->food.box; else if (g_strcmp0 (name, chooser->travel.first) == 0) chooser->box = chooser->travel.box; else if (g_strcmp0 (name, chooser->activities.first) == 0) chooser->box = chooser->activities.box; else if (g_strcmp0 (name, chooser->objects.first) == 0) chooser->box = chooser->objects.box; else if (g_strcmp0 (name, chooser->symbols.first) == 0) chooser->box = chooser->symbols.box; else if (g_strcmp0 (name, chooser->flags.first) == 0) chooser->box = chooser->flags.box; add_emoji (chooser->box, FALSE, item, 0, chooser); g_variant_unref (item); now = g_get_monotonic_time (); if (now > start + 8000) return G_SOURCE_CONTINUE; } /* We scroll to the top on show, so check the right button for the 1st time */ gtk_widget_set_state_flags (chooser->recent.button, GTK_STATE_FLAG_CHECKED, FALSE); g_variant_iter_free (chooser->iter); chooser->iter = NULL; chooser->box = NULL; chooser->populate_idle = 0; return G_SOURCE_REMOVE; } static void adj_value_changed (GtkAdjustment *adj, gpointer data) { DinoEmojiChooser *chooser = data; double value = gtk_adjustment_get_value (adj); EmojiSection const *sections[] = { &chooser->recent, &chooser->people, &chooser->body, &chooser->nature, &chooser->food, &chooser->travel, &chooser->activities, &chooser->objects, &chooser->symbols, &chooser->flags, }; EmojiSection const *select_section = sections[0]; gsize i; /* Figure out which section the current scroll position is within */ for (i = 0; i < G_N_ELEMENTS (sections); ++i) { EmojiSection const *section = sections[i]; GtkAllocation alloc; if (section->heading) gtk_widget_get_allocation (section->heading, &alloc); else gtk_widget_get_allocation (section->box, &alloc); if (value < alloc.y - BOX_SPACE) break; select_section = section; } /* Un/Check the section buttons accordingly */ for (i = 0; i < G_N_ELEMENTS (sections); ++i) { EmojiSection const *section = sections[i]; if (section == select_section) gtk_widget_set_state_flags (section->button, GTK_STATE_FLAG_CHECKED, FALSE); else gtk_widget_unset_state_flags (section->button, GTK_STATE_FLAG_CHECKED); } } static gboolean filter_func (GtkFlowBoxChild *child, gpointer data) { EmojiSection *section = data; DinoEmojiChooser *chooser; GVariant *emoji_data; const char *text; const char *name; gboolean res; res = TRUE; chooser = DINO_EMOJI_CHOOSER (gtk_widget_get_ancestor (GTK_WIDGET (child), GTK_TYPE_EMOJI_CHOOSER)); text = gtk_entry_get_text (GTK_ENTRY (chooser->search_entry)); emoji_data = (GVariant *) g_object_get_data (G_OBJECT (child), "emoji-data"); if (text[0] == 0) goto out; if (!emoji_data) goto out; g_variant_get_child (emoji_data, 1, "&s", &name); res = g_str_match_string (text, name, TRUE); out: if (res) section->empty = FALSE; return res; } static void invalidate_section (EmojiSection *section) { section->empty = TRUE; gtk_flow_box_invalidate_filter (GTK_FLOW_BOX (section->box)); } static void update_headings (DinoEmojiChooser *chooser) { gtk_widget_set_visible (chooser->people.heading, !chooser->people.empty); gtk_widget_set_visible (chooser->people.box, !chooser->people.empty); gtk_widget_set_visible (chooser->body.heading, !chooser->body.empty); gtk_widget_set_visible (chooser->body.box, !chooser->body.empty); gtk_widget_set_visible (chooser->nature.heading, !chooser->nature.empty); gtk_widget_set_visible (chooser->nature.box, !chooser->nature.empty); gtk_widget_set_visible (chooser->food.heading, !chooser->food.empty); gtk_widget_set_visible (chooser->food.box, !chooser->food.empty); gtk_widget_set_visible (chooser->travel.heading, !chooser->travel.empty); gtk_widget_set_visible (chooser->travel.box, !chooser->travel.empty); gtk_widget_set_visible (chooser->activities.heading, !chooser->activities.empty); gtk_widget_set_visible (chooser->activities.box, !chooser->activities.empty); gtk_widget_set_visible (chooser->objects.heading, !chooser->objects.empty); gtk_widget_set_visible (chooser->objects.box, !chooser->objects.empty); gtk_widget_set_visible (chooser->symbols.heading, !chooser->symbols.empty); gtk_widget_set_visible (chooser->symbols.box, !chooser->symbols.empty); gtk_widget_set_visible (chooser->flags.heading, !chooser->flags.empty); gtk_widget_set_visible (chooser->flags.box, !chooser->flags.empty); if (chooser->recent.empty && chooser->people.empty && chooser->body.empty && chooser->nature.empty && chooser->food.empty && chooser->travel.empty && chooser->activities.empty && chooser->objects.empty && chooser->symbols.empty && chooser->flags.empty) gtk_stack_set_visible_child_name (GTK_STACK (chooser->stack), "empty"); else gtk_stack_set_visible_child_name (GTK_STACK (chooser->stack), "list"); } static void search_changed (GtkEntry *entry, gpointer data) { DinoEmojiChooser *chooser = data; invalidate_section (&chooser->recent); invalidate_section (&chooser->people); invalidate_section (&chooser->body); invalidate_section (&chooser->nature); invalidate_section (&chooser->food); invalidate_section (&chooser->travel); invalidate_section (&chooser->activities); invalidate_section (&chooser->objects); invalidate_section (&chooser->symbols); invalidate_section (&chooser->flags); update_headings (chooser); } static void setup_section (DinoEmojiChooser *chooser, EmojiSection *section, const char *first, const char *icon) { GtkAdjustment *adj; GtkWidget *image; section->first = first; image = gtk_bin_get_child (GTK_BIN (section->button)); gtk_image_set_from_icon_name (GTK_IMAGE (image), icon, GTK_ICON_SIZE_BUTTON); adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (chooser->scrolled_window)); gtk_container_set_focus_vadjustment (GTK_CONTAINER (section->box), adj); gtk_flow_box_set_filter_func (GTK_FLOW_BOX (section->box), filter_func, section, NULL); g_signal_connect (section->button, "clicked", G_CALLBACK (scroll_to_section), section); } static void dino_emoji_chooser_init (DinoEmojiChooser *chooser) { GtkAdjustment *adj; chooser->settings = g_settings_new ("org.gtk.Settings.EmojiChooser"); gtk_widget_init_template (GTK_WIDGET (chooser)); /* Get a reasonable maximum width for an emoji. We do this to * skip overly wide fallback rendering for certain emojis the * font does not contain and therefore end up being rendered * as multiply glyphs. */ { PangoLayout *layout = gtk_widget_create_pango_layout (GTK_WIDGET (chooser), "๐Ÿ™‚"); PangoAttrList *attrs; PangoRectangle rect; attrs = pango_attr_list_new (); pango_attr_list_insert (attrs, pango_attr_scale_new (PANGO_SCALE_X_LARGE)); pango_layout_set_attributes (layout, attrs); pango_attr_list_unref (attrs); pango_layout_get_extents (layout, &rect, NULL); chooser->emoji_max_width = rect.width; g_object_unref (layout); } chooser->recent_long_press = gtk_gesture_long_press_new (chooser->recent.box); g_signal_connect (chooser->recent_long_press, "pressed", G_CALLBACK (long_pressed_cb), chooser); chooser->recent_multi_press = gtk_gesture_multi_press_new (chooser->recent.box); gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (chooser->recent_multi_press), GDK_BUTTON_SECONDARY); g_signal_connect (chooser->recent_multi_press, "pressed", G_CALLBACK (pressed_cb), chooser); chooser->people_long_press = gtk_gesture_long_press_new (chooser->people.box); g_signal_connect (chooser->people_long_press, "pressed", G_CALLBACK (long_pressed_cb), chooser); chooser->people_multi_press = gtk_gesture_multi_press_new (chooser->people.box); gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (chooser->people_multi_press), GDK_BUTTON_SECONDARY); g_signal_connect (chooser->people_multi_press, "pressed", G_CALLBACK (pressed_cb), chooser); chooser->body_long_press = gtk_gesture_long_press_new (chooser->body.box); g_signal_connect (chooser->body_long_press, "pressed", G_CALLBACK (long_pressed_cb), chooser); chooser->body_multi_press = gtk_gesture_multi_press_new (chooser->body.box); gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (chooser->body_multi_press), GDK_BUTTON_SECONDARY); g_signal_connect (chooser->body_multi_press, "pressed", G_CALLBACK (pressed_cb), chooser); adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (chooser->scrolled_window)); g_signal_connect (adj, "value-changed", G_CALLBACK (adj_value_changed), chooser); setup_section (chooser, &chooser->recent, NULL, "emoji-recent-symbolic"); setup_section (chooser, &chooser->people, "grinning face", "emoji-people-symbolic"); setup_section (chooser, &chooser->body, "selfie", "emoji-body-symbolic"); setup_section (chooser, &chooser->nature, "monkey face", "emoji-nature-symbolic"); setup_section (chooser, &chooser->food, "grapes", "emoji-food-symbolic"); setup_section (chooser, &chooser->travel, "globe showing Europe-Africa", "emoji-travel-symbolic"); setup_section (chooser, &chooser->activities, "jack-o-lantern", "emoji-activities-symbolic"); setup_section (chooser, &chooser->objects, "muted speaker", "emoji-objects-symbolic"); setup_section (chooser, &chooser->symbols, "ATM sign", "emoji-symbols-symbolic"); setup_section (chooser, &chooser->flags, "chequered flag", "emoji-flags-symbolic"); populate_recent_section (chooser); chooser->populate_idle = g_idle_add (populate_emoji_chooser, chooser); g_source_set_name_by_id (chooser->populate_idle, "[gtk] populate_emoji_chooser"); } static void dino_emoji_chooser_show (GtkWidget *widget) { DinoEmojiChooser *chooser = DINO_EMOJI_CHOOSER (widget); GtkAdjustment *adj; GTK_WIDGET_CLASS (dino_emoji_chooser_parent_class)->show (widget); adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (chooser->scrolled_window)); gtk_adjustment_set_value (adj, 0); gtk_entry_set_text (GTK_ENTRY (chooser->search_entry), ""); } static gboolean dino_emoji_chooser_button_release (GtkWidget *widget, GdkEventButton *event) { DinoEmojiChooserClass *klass = DINO_EMOJI_CHOOSER_GET_CLASS(widget); GtkWidget *event_widget = gtk_get_event_widget ((GdkEvent *) event); if (!event_widget && event->window != gtk_widget_get_window (widget)) { return GDK_EVENT_PROPAGATE; } return klass->popover_button_release_event (widget, event); } static void dino_emoji_chooser_class_init (DinoEmojiChooserClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->finalize = dino_emoji_chooser_finalize; widget_class->show = dino_emoji_chooser_show; klass->popover_button_release_event = widget_class->button_release_event; widget_class->button_release_event = dino_emoji_chooser_button_release; signals[EMOJI_PICKED] = g_signal_new ("emoji-picked", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING|G_SIGNAL_TYPE_STATIC_SCOPE); gtk_widget_class_set_template_from_resource (widget_class, "/im/dino/Dino/emojichooser.ui"); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, search_entry); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, stack); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, scrolled_window); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, recent.box); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, recent.button); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, people.box); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, people.heading); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, people.button); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, body.box); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, body.heading); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, body.button); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, nature.box); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, nature.heading); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, nature.button); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, food.box); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, food.heading); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, food.button); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, travel.box); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, travel.heading); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, travel.button); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, activities.box); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, activities.heading); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, activities.button); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, objects.box); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, objects.heading); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, objects.button); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, symbols.box); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, symbols.heading); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, symbols.button); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, flags.box); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, flags.heading); gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, flags.button); gtk_widget_class_bind_template_callback (widget_class, emoji_activated); gtk_widget_class_bind_template_callback (widget_class, search_changed); } DinoEmojiChooser * dino_emoji_chooser_new (void) { return DINO_EMOJI_CHOOSER (g_object_new (GTK_TYPE_EMOJI_CHOOSER, NULL)); } dino-0.1.0/main/src/emojichooser.h0000644000000000000000000000330513614354364015523 0ustar rootroot/* gtkemojichooser.h: An Emoji chooser widget * Copyright 2017, Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #pragma once #include G_BEGIN_DECLS #define GTK_TYPE_EMOJI_CHOOSER (dino_emoji_chooser_get_type ()) #define DINO_EMOJI_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_EMOJI_CHOOSER, DinoEmojiChooser)) #define DINO_EMOJI_CHOOSER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_EMOJI_CHOOSER, DinoEmojiChooserClass)) #define GTK_IS_EMOJI_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_EMOJI_CHOOSER)) #define GTK_IS_EMOJI_CHOOSER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_EMOJI_CHOOSER)) #define DINO_EMOJI_CHOOSER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_EMOJI_CHOOSER, DinoEmojiChooserClass)) typedef struct _DinoEmojiChooser DinoEmojiChooser; typedef struct _DinoEmojiChooserClass DinoEmojiChooserClass; GType dino_emoji_chooser_get_type (void) G_GNUC_CONST; DinoEmojiChooser *dino_emoji_chooser_new (void); G_END_DECLS dino-0.1.0/main/src/main.vala0000644000000000000000000000150313614354364014453 0ustar rootrootusing Dino.Entities; using Dino.Ui; extern const string GETTEXT_PACKAGE; extern const string LOCALE_INSTALL_DIR; namespace Dino { void main(string[] args) { try{ string? exec_path = args.length > 0 ? args[0] : null; SearchPathGenerator search_path_generator = new SearchPathGenerator(exec_path); Intl.textdomain(GETTEXT_PACKAGE); internationalize(GETTEXT_PACKAGE, search_path_generator.get_locale_path(GETTEXT_PACKAGE, LOCALE_INSTALL_DIR)); Gtk.init(ref args); Dino.Ui.Application app = new Dino.Ui.Application() { search_path_generator=search_path_generator }; Plugins.Loader loader = new Plugins.Loader(app); loader.loadAll(); app.run(args); loader.shutdown(); } catch (Error e) { warning(@"Fatal error: $(e.message)"); } } } dino-0.1.0/main/src/ui/0000755000000000000000000000000013614354364013300 5ustar rootrootdino-0.1.0/main/src/ui/add_conversation/0000755000000000000000000000000013614354364016622 5ustar rootrootdino-0.1.0/main/src/ui/add_conversation/add_conference_dialog.vala0000644000000000000000000002026013614354364023725 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; using Xmpp; using Xmpp.Xep; namespace Dino.Ui { public class AddConferenceDialog : Gtk.Dialog { private Stack stack = new Stack(); private Button cancel_button; private Button ok_button; private Label cancel_label = new Label(_("Cancel")) {visible=true}; private Image cancel_image = new Image.from_icon_name("go-previous-symbolic", IconSize.MENU) {visible=true}; private SelectJidFragment select_fragment; private ConferenceDetailsFragment details_fragment; private ConferenceList conference_list; private StreamInteractor stream_interactor; public AddConferenceDialog(StreamInteractor stream_interactor) { Object(use_header_bar : Util.use_csd() ? 1 : 0); this.title = _("Join Channel"); this.modal = true; this.stream_interactor = stream_interactor; stack.visible = true; stack.vhomogeneous = false; get_content_area().add(stack); setup_headerbar(); setup_jid_add_view(); setup_conference_details_view(); show_jid_add_view(); } private void show_jid_add_view() { // Rewire headerbar (if CSD) if (Util.use_csd()) { if (cancel_image.get_parent() != null) cancel_button.remove(cancel_image); cancel_button.add(cancel_label); cancel_button.clicked.disconnect(show_jid_add_view); cancel_button.clicked.connect(on_cancel); ok_button.label = _("Next"); ok_button.sensitive = select_fragment.done; ok_button.clicked.connect(on_next_button_clicked); details_fragment.fragment_active = false; details_fragment.notify["done"].disconnect(set_ok_sensitive_from_details); select_fragment.notify["done"].connect(set_ok_sensitive_from_select); } stack.transition_type = StackTransitionType.SLIDE_RIGHT; stack.set_visible_child_name("select"); } private void show_conference_details_view() { // Rewire headerbar (if CSD) if (Util.use_csd()) { if (cancel_label.get_parent() != null) cancel_button.remove(cancel_label); cancel_button.add(cancel_image); cancel_button.clicked.disconnect(on_cancel); cancel_button.clicked.connect(show_jid_add_view); ok_button.label = _("Join"); ok_button.sensitive = details_fragment.done; ok_button.clicked.disconnect(on_next_button_clicked); details_fragment.fragment_active = true; select_fragment.notify["done"].disconnect(set_ok_sensitive_from_select); details_fragment.notify["done"].connect(set_ok_sensitive_from_details); } stack.transition_type = StackTransitionType.SLIDE_LEFT; stack.set_visible_child_name("details"); animate_window_resize(); } private void setup_headerbar() { cancel_button = new Button() { visible=true }; ok_button = new Button() { can_focus=true, can_default=true, visible=true }; ok_button.get_style_context().add_class("suggested-action"); if (Util.use_csd()) { HeaderBar header_bar = get_header_bar() as HeaderBar; header_bar.show_close_button = false; header_bar.pack_start(cancel_button); header_bar.pack_end(ok_button); ok_button.has_default = true; } } private void setup_jid_add_view() { conference_list = new ConferenceList(stream_interactor); conference_list.row_activated.connect(() => { ok_button.clicked(); }); select_fragment = new SelectJidFragment(stream_interactor, conference_list, stream_interactor.get_accounts()); select_fragment.add_jid.connect((row) => { AddGroupchatDialog dialog = new AddGroupchatDialog(stream_interactor); dialog.set_transient_for(this); dialog.present(); }); select_fragment.remove_jid.connect((row) => { ConferenceListRow conference_row = row as ConferenceListRow; stream_interactor.get_module(MucManager.IDENTITY).remove_bookmark(conference_row.account, conference_row.bookmark); }); Box wrap_box = new Box(Orientation.VERTICAL, 0) { visible=true }; wrap_box.add(select_fragment); stack.add_named(wrap_box, "select"); if (!Util.use_csd()) { Box box = new Box(Orientation.HORIZONTAL, 5) { halign=Align.END, margin_bottom=15, margin_start=80, margin_end=80, visible=true }; Button ok_button = new Button() { label=_("Next"), sensitive=false, halign = Align.END, can_focus=true, can_default=true, visible=true }; ok_button.get_style_context().add_class("suggested-action"); ok_button.clicked.connect(on_next_button_clicked); select_fragment.notify["done"].connect(() => { ok_button.sensitive = select_fragment.done; }); Button cancel_button = new Button() { label=_("Cancel"), halign=Align.START, visible=true }; cancel_button.clicked.connect(on_cancel); box.add(cancel_button); box.add(ok_button); wrap_box.add(box); ok_button.has_default = true; } } private void setup_conference_details_view() { details_fragment = new ConferenceDetailsFragment(stream_interactor) { ok_button=ok_button }; details_fragment.joined.connect(() => this.close()); Box wrap_box = new Box(Orientation.VERTICAL, 0) { visible=true }; wrap_box.add(details_fragment); if (!Util.use_csd()) { Box box = new Box(Orientation.HORIZONTAL, 5) { halign=Align.END, margin_bottom=15, margin_start=80, margin_end=80, visible=true }; Button ok_button = new Button() { label=_("Join"), halign = Align.END, can_focus=true, can_default=true, visible=true }; ok_button.get_style_context().add_class("suggested-action"); details_fragment.notify["done"].connect(() => { ok_button.sensitive = select_fragment.done; }); details_fragment.ok_button = ok_button; Button cancel_button = new Button() { label=_("Back"), halign=Align.START, visible=true }; cancel_button.clicked.connect(show_jid_add_view); box.add(cancel_button); box.add(ok_button); wrap_box.add(box); } stack.add_named(wrap_box, "details"); } private void set_ok_sensitive_from_select() { ok_button.sensitive = select_fragment.done; } private void set_ok_sensitive_from_details() { ok_button.sensitive = details_fragment.done; } private void on_next_button_clicked() { details_fragment.clear(); ListRow? row = conference_list.get_selected_row() as ListRow; ConferenceListRow? conference_row = conference_list.get_selected_row() as ConferenceListRow; if (conference_row != null) { details_fragment.account = conference_row.account; details_fragment.jid = conference_row.bookmark.jid.to_string(); details_fragment.nick = conference_row.bookmark.nick; if (conference_row.bookmark.password != null) details_fragment.password = conference_row.bookmark.password; ok_button.grab_focus(); } else if (row != null) { details_fragment.account = row.account; details_fragment.jid = row.jid.to_string(); } show_conference_details_view(); } private void on_cancel() { close(); } private void animate_window_resize() { int def_height, curr_width, curr_height; get_size(out curr_width, out curr_height); stack.get_preferred_height(null, out def_height); int difference = def_height - curr_height; Timer timer = new Timer(); Timeout.add((int) (stack.transition_duration / 30), () => { ulong microsec; timer.elapsed(out microsec); ulong millisec = microsec / 1000; double partial = double.min(1, (double) millisec / stack.transition_duration); resize(curr_width, (int) (curr_height + difference * partial)); return millisec < stack.transition_duration; }); } } } dino-0.1.0/main/src/ui/add_conversation/add_contact_dialog.vala0000644000000000000000000000402213614354364023247 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; using Xmpp; namespace Dino.Ui { [GtkTemplate (ui = "/im/dino/Dino/add_conversation/add_contact_dialog.ui")] protected class AddContactDialog : Gtk.Dialog { public Account? account { get { return account_combobox.selected; } set { account_combobox.selected = value; } } public string jid { get { return jid_entry.text; } set { jid_entry.text = value; } } [GtkChild] private AccountComboBox account_combobox; [GtkChild] private Button ok_button; [GtkChild] private Button cancel_button; [GtkChild] private Entry jid_entry; [GtkChild] private Entry alias_entry; private StreamInteractor stream_interactor; public AddContactDialog(StreamInteractor stream_interactor) { Object(use_header_bar : 1); this.stream_interactor = stream_interactor; account_combobox.initialize(stream_interactor); cancel_button.clicked.connect(() => { close(); }); ok_button.clicked.connect(on_ok_button_clicked); jid_entry.changed.connect(on_jid_entry_changed); } private void on_ok_button_clicked() { string? alias = alias_entry.text == "" ? null : alias_entry.text; try { Jid jid = new Jid(jid_entry.text); stream_interactor.get_module(RosterManager.IDENTITY).add_jid(account, jid, alias); stream_interactor.get_module(PresenceManager.IDENTITY).request_subscription(account, jid); close(); } catch (InvalidJidError e) { warning("Tried to add contact with invalid Jid: %s", e.message); } } private void on_jid_entry_changed() { try { Jid parsed_jid = new Jid(jid_entry.text); ok_button. sensitive = parsed_jid != null && parsed_jid.resourcepart == null && stream_interactor.get_module(RosterManager.IDENTITY).get_roster_item(account, parsed_jid) == null; } catch (InvalidJidError e) { ok_button.sensitive = false; } } } } dino-0.1.0/main/src/ui/add_conversation/add_groupchat_dialog.vala0000644000000000000000000000665713614354364023630 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; using Xmpp; namespace Dino.Ui { [GtkTemplate (ui = "/im/dino/Dino/add_conversation/add_groupchat_dialog.ui")] protected class AddGroupchatDialog : Gtk.Dialog { [GtkChild] private Stack accounts_stack; [GtkChild] private AccountComboBox account_combobox; [GtkChild] private Label account_label; [GtkChild] private Button ok_button; [GtkChild] private Button cancel_button; [GtkChild] private Entry jid_entry; [GtkChild] private Entry alias_entry; [GtkChild] private Entry nick_entry; private StreamInteractor stream_interactor; private Conference? edit_conference = null; private bool alias_entry_changed = false; public AddGroupchatDialog(StreamInteractor stream_interactor) { Object(use_header_bar : 1); this.stream_interactor = stream_interactor; ok_button.label = _("Add"); ok_button.get_style_context().add_class("suggested-action"); // TODO why doesn't it work in XML accounts_stack.set_visible_child_name("combobox"); account_combobox.initialize(stream_interactor); cancel_button.clicked.connect(() => { close(); }); ok_button.clicked.connect(on_ok_button_clicked); jid_entry.key_release_event.connect(on_jid_key_release); nick_entry.key_release_event.connect(check_ok); } public AddGroupchatDialog.for_conference(StreamInteractor stream_interactor, Account account, Conference conference) { this(stream_interactor); edit_conference = conference; ok_button.label = _("Save"); ok_button.sensitive = true; accounts_stack.set_visible_child_name("label"); account_label.label = account.bare_jid.to_string(); account_combobox.selected = account; jid_entry.text = conference.jid.to_string(); nick_entry.text = conference.nick ?? ""; alias_entry.text = conference.name; } private bool on_jid_key_release() { check_ok(); if (!alias_entry_changed) { try { Jid parsed_jid = new Jid(jid_entry.text); alias_entry.text = parsed_jid != null && parsed_jid.localpart != null ? parsed_jid.localpart : jid_entry.text; } catch (InvalidJidError e) { alias_entry.text = jid_entry.text; } } return false; } private bool check_ok() { try { Jid parsed_jid = new Jid(jid_entry.text); ok_button.sensitive = parsed_jid != null && parsed_jid.localpart != null && parsed_jid.resourcepart == null; } catch (InvalidJidError e) { ok_button.sensitive = false; } return false; } private void on_ok_button_clicked() { try { Conference conference = new Conference(); conference.jid = new Jid(jid_entry.text); conference.nick = nick_entry.text != "" ? nick_entry.text : null; conference.name = alias_entry.text; if (edit_conference == null) { stream_interactor.get_module(MucManager.IDENTITY).add_bookmark(account_combobox.selected, conference); } else { stream_interactor.get_module(MucManager.IDENTITY).replace_bookmark(account_combobox.selected, edit_conference, conference); } close(); } catch (InvalidJidError e) { warning("Ignoring invalid conference Jid: %s", e.message); } } } } dino-0.1.0/main/src/ui/add_conversation/conference_details_fragment.vala0000644000000000000000000002074613614354364025177 0ustar rootrootusing Gdk; using Gtk; using Dino.Entities; using Xmpp; using Xmpp.Xep; namespace Dino.Ui { [GtkTemplate (ui = "/im/dino/Dino/add_conversation/conference_details_fragment.ui")] protected class ConferenceDetailsFragment : Box { public signal void joined(); public bool done { get { try { Jid parsed_jid = new Jid(jid); return parsed_jid.localpart != null && parsed_jid.resourcepart == null && nick != null; } catch (InvalidJidError e) { return false; } } private set {} } public Account account { owned get { return account_combobox.selected; } set { accounts_label.label = value.bare_jid.to_string(); account_combobox.selected = value; if (nick == null && value.alias != null) { nick = value.alias; } accounts_stack.set_visible_child_name("label"); } } public string jid { get { return jid_entry.text; } set { jid_label.label = value; jid_entry.text = value; jid_stack.set_visible_child_name("label"); } } public string? nick { get { return nick_entry.text != "" ? nick_entry.text : null; } set { nick_label.label = value ?? ""; nick_entry.text = value ?? ""; nick_stack.set_visible_child_name("label"); } } public string? password { get { return password_entry.text == "" ? null : password_entry.text; } set { password_label.label = value; password_entry.text = value; nick_stack.set_visible_child_name("label"); } } public bool fragment_active { get; set; default=true; } [GtkChild] private Stack accounts_stack; [GtkChild] private Button accounts_button; [GtkChild] private Label accounts_label; [GtkChild] private AccountComboBox account_combobox; [GtkChild] private Stack jid_stack; [GtkChild] private Button jid_button; [GtkChild] private Label jid_label; [GtkChild] private Entry jid_entry; [GtkChild] private Stack nick_stack; [GtkChild] private Button nick_button; [GtkChild] private Label nick_label; [GtkChild] private Entry nick_entry; [GtkChild] private Stack password_stack; [GtkChild] private Button password_button; [GtkChild] private Label password_label; [GtkChild] private Label password_text_label; [GtkChild] private Entry password_entry; [GtkChild] private Revealer notification_revealer; [GtkChild] private Button notification_button; [GtkChild] private Label notification_label; private StreamInteractor stream_interactor; private Button ok_button_; public Button ok_button { get { return ok_button_; } set { if (value != null) { value.clicked.connect(() => { on_ok_button_clicked.begin(); }); ok_button_ = value; } } } public ConferenceDetailsFragment(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; account_combobox.initialize(stream_interactor); accounts_button.clicked.connect(() => { set_active_stack(accounts_stack); }); jid_button.clicked.connect(() => { set_active_stack(jid_stack); }); nick_button.clicked.connect(() => { set_active_stack(nick_stack); }); password_button.clicked.connect(() => { set_active_stack(password_stack); }); account_combobox.changed.connect(() => { accounts_label.label = account_combobox.selected.bare_jid.to_string(); }); accounts_label.label = account_combobox.selected.bare_jid.to_string(); jid_entry.key_release_event.connect(on_jid_key_release_event); nick_entry.key_release_event.connect(on_nick_key_release_event); password_entry.key_release_event.connect(on_password_key_release_event); jid_entry.key_release_event.connect(() => { done = true; return false; }); // just for notifying nick_entry.key_release_event.connect(() => { done = true; return false; }); notification_button.clicked.connect(() => { notification_revealer.set_reveal_child(false); }); clear(); } public void clear() { jid = ""; nick = ""; password = ""; password_text_label.visible = false; password_stack.visible = false; notification_revealer.set_reveal_child(false); reset_editable(); } public void reset_editable() { jid_stack.set_visible_child_name("entry"); accounts_stack.set_visible_child_name("entry"); nick_stack.set_visible_child_name("entry"); password_stack.set_visible_child_name("entry"); } private async void on_ok_button_clicked() { if (!fragment_active) return; ok_button.label = _("Joiningโ€ฆ"); ok_button.sensitive = false; string label_text = ""; try { Jid parsed_jid = new Jid(jid); Muc.JoinResult? join_result = yield stream_interactor.get_module(MucManager.IDENTITY).join(account, parsed_jid, nick, password); ok_button.label = _("Join"); ok_button.sensitive = true; if (join_result == null || join_result.nick != null) { Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(parsed_jid, account, Conversation.Type.GROUPCHAT); Application app = GLib.Application.get_default() as Application; app.controller.select_conversation(conversation);joined(); return; } if (join_result.muc_error != null) { switch (join_result.muc_error) { case Muc.MucEnterError.PASSWORD_REQUIRED: label_text = _("Password required to enter room"); password_text_label.visible = true; password_stack.visible = true; break; case Muc.MucEnterError.BANNED: label_text = _("Banned from joining or creating conference"); break; case Muc.MucEnterError.ROOM_DOESNT_EXIST: label_text = _("Room does not exist"); break; case Muc.MucEnterError.CREATION_RESTRICTED: label_text = _("Not allowed to create room"); break; case Muc.MucEnterError.NOT_IN_MEMBER_LIST: label_text = _("Members-only room"); break; case Muc.MucEnterError.USE_RESERVED_ROOMNICK: case Muc.MucEnterError.NICK_CONFLICT: label_text = _("Choose a different nick"); break; case Muc.MucEnterError.OCCUPANT_LIMIT_REACHED: label_text = _("Too many occupants in room"); break; } } else if (join_result.stanza_error != null) { label_text = _("Could not connect to %s").printf((new Jid(jid)).domainpart); } } catch (InvalidJidError e) { label_text = _("Invalid address"); } notification_label.label = label_text; notification_revealer.set_reveal_child(true); } private bool on_jid_key_release_event(EventKey event) { jid_label.label = jid_entry.text; if (event.keyval == Key.Return) jid_stack.set_visible_child_name("label"); return false; } private bool on_nick_key_release_event(EventKey event) { nick_label.label = nick_entry.text; if (event.keyval == Key.Return) nick_stack.set_visible_child_name("label"); return false; } private bool on_password_key_release_event(EventKey event) { string filler = ""; for (int i = 0; i < password_entry.text.length; i++) filler += password_entry.get_invisible_char().to_string(); password_label.label = filler; if (event.keyval == Key.Return) password_stack.set_visible_child_name("label"); return false; } private void set_active_stack(Stack stack) { stack.set_visible_child_name("entry"); if (stack != accounts_stack) accounts_stack.set_visible_child_name("label"); if (stack != jid_stack) jid_stack.set_visible_child_name("label"); if (stack != nick_stack) nick_stack.set_visible_child_name("label"); if (stack != password_stack) password_stack.set_visible_child_name("label"); } } } dino-0.1.0/main/src/ui/add_conversation/conference_list.vala0000644000000000000000000001067313614354364022640 0ustar rootrootusing Gee; using Gtk; using Xmpp; using Xmpp.Xep.Bookmarks; using Dino.Entities; namespace Dino.Ui { protected class ConferenceList : FilterableList { public signal void conversation_selected(Conversation? conversation); private StreamInteractor stream_interactor; private HashMap> lists = new HashMap>(Account.hash_func, Account.equals_func); private HashMap> widgets = new HashMap>(Account.hash_func, Account.equals_func); public ConferenceList(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; set_filter_func(filter); set_header_func(header); set_sort_func(sort); stream_interactor.get_module(MucManager.IDENTITY).bookmarks_updated.connect((account, conferences) => { lists[account] = conferences; refresh_conferences(); }); foreach (Account account in stream_interactor.get_accounts()) { stream_interactor.get_module(MucManager.IDENTITY).get_bookmarks.begin(account, (_, res) => { Set? conferences = stream_interactor.get_module(MucManager.IDENTITY).get_bookmarks.end(res); set_bookmarks(account, conferences); }); } stream_interactor.get_module(MucManager.IDENTITY).conference_added.connect(add_conference); stream_interactor.get_module(MucManager.IDENTITY).conference_removed.connect(remove_conference); } private void add_conference(Account account, Conference conference) { if (!widgets.has_key(account)) { widgets[account] = new HashMap(Jid.hash_func, Jid.equals_func); } var widget = new ConferenceListRow(stream_interactor, conference, account); widgets[account][conference.jid] = widget; add(widget); } private void remove_conference(Account account, Jid jid) { if (widgets.has_key(account) && widgets[account].has_key(jid)) { widgets[account][jid].destroy(); widgets[account].unset(jid); } } public void refresh_conferences() { @foreach((widget) => { remove(widget); }); foreach (Account account in lists.keys) { foreach (Conference conference in lists[account]) { add_conference(account, conference); } } } private void set_bookmarks(Account account, Set? conferences) { if (conferences == null) { lists.unset(account); } else { lists[account] = conferences; } refresh_conferences(); } private void header(ListBoxRow row, ListBoxRow? before_row) { if (row.get_header() == null && before_row != null) { row.set_header(new Separator(Orientation.HORIZONTAL)); } } private bool filter(ListBoxRow r) { if (r.get_type().is_a(typeof(ListRow))) { ListRow row = r as ListRow; if (filter_values != null) { foreach (string filter in filter_values) { if (!(row.name_label.label.down().contains(filter.down()) || row.jid.to_string().down().contains(filter.down()))) { return false; } } } } return true; } public override int sort(ListBoxRow row1, ListBoxRow row2) { ListRow c1 = (row1 as ListRow); ListRow c2 = (row2 as ListRow); return c1.name_label.label.collate(c2.name_label.label); } } internal class ConferenceListRow : ListRow { public Conference bookmark; public ConferenceListRow(StreamInteractor stream_interactor, Conference bookmark, Account account) { this.jid = bookmark.jid; this.account = account; this.bookmark = bookmark; name_label.label = bookmark.name != null && bookmark.name != "" ? bookmark.name : bookmark.jid.to_string(); if (stream_interactor.get_accounts().size > 1) { via_label.label = "via " + account.bare_jid.to_string(); } else if (bookmark.name != null && bookmark.name != bookmark.jid.to_string()) { via_label.label = bookmark.jid.to_string(); } else { via_label.visible = false; } image.set_conversation(stream_interactor, new Conversation(jid, account, Conversation.Type.GROUPCHAT)); } } } dino-0.1.0/main/src/ui/add_conversation/list_row.vala0000644000000000000000000000231513614354364021332 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; using Xmpp; namespace Dino.Ui { [GtkTemplate (ui = "/im/dino/Dino/add_conversation/list_row.ui")] public class ListRow : ListBoxRow { [GtkChild] public AvatarImage image; [GtkChild] public Label name_label; [GtkChild] public Label via_label; public Jid? jid; public Account? account; public ListRow() {} public ListRow.from_jid(StreamInteractor stream_interactor, Jid jid, Account account, bool show_account) { this.jid = jid; this.account = account; Conversation conv = new Conversation(jid, account, Conversation.Type.CHAT); string display_name = Util.get_conversation_display_name(stream_interactor, conv); if (show_account && stream_interactor.get_accounts().size > 1) { via_label.label = @"via $(account.bare_jid)"; this.has_tooltip = true; set_tooltip_text(jid.to_string()); } else if (display_name != jid.bare_jid.to_string()){ via_label.label = jid.bare_jid.to_string(); } else { via_label.visible = false; } name_label.label = display_name; image.set_conversation(stream_interactor, conv); } } } dino-0.1.0/main/src/ui/add_conversation/roster_list.vala0000644000000000000000000000664113614354364022047 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; using Xmpp; namespace Dino.Ui { protected class RosterList : FilterableList { public signal void conversation_selected(Conversation? conversation); private StreamInteractor stream_interactor; private Gee.List accounts; private ulong[] handler_ids = new ulong[0]; private HashMap> rows = new HashMap>(Account.hash_func, Account.equals_func); public RosterList(StreamInteractor stream_interactor, Gee.List accounts) { this.stream_interactor = stream_interactor; this.accounts = accounts; set_filter_func(filter); set_header_func(header); set_sort_func(sort); handler_ids += stream_interactor.get_module(RosterManager.IDENTITY).removed_roster_item.connect( (account, jid, roster_item) => { if (accounts.contains(account)) { on_removed_roster_item(account, jid, roster_item); } }); handler_ids += stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.connect( (account, jid, roster_item) => { if (accounts.contains(account)) { on_updated_roster_item(account, jid, roster_item); } }); destroy.connect(() => { foreach (ulong handler_id in handler_ids) stream_interactor.get_module(RosterManager.IDENTITY).disconnect(handler_id); }); foreach (Account a in accounts) fetch_roster_items(a); } private void on_removed_roster_item(Account account, Jid jid, Roster.Item roster_item) { if (rows.has_key(account) && rows[account].has_key(jid)) { remove(rows[account][jid]); rows[account].unset(jid); } } private void on_updated_roster_item(Account account, Jid jid, Roster.Item roster_item) { on_removed_roster_item(account, jid, roster_item); ListRow row = new ListRow.from_jid(stream_interactor, roster_item.jid, account, accounts.size > 1); rows[account][jid] = row; add(row); invalidate_sort(); invalidate_filter(); } private void fetch_roster_items(Account account) { rows[account] = new HashMap(Jid.hash_func, Jid.equals_func); foreach (Roster.Item roster_item in stream_interactor.get_module(RosterManager.IDENTITY).get_roster(account)) { on_updated_roster_item(account, roster_item.jid, roster_item); } } private void header(ListBoxRow row, ListBoxRow? before_row) { if (row.get_header() == null && before_row != null) { row.set_header(new Separator(Orientation.HORIZONTAL)); } } private bool filter(ListBoxRow r) { if (r.get_type().is_a(typeof(ListRow))) { ListRow row = r as ListRow; if (filter_values != null) { foreach (string filter in filter_values) { if (!(row.name_label.label.down().contains(filter.down()) || row.jid.to_string().down().contains(filter.down()))) { return false; } } } } return true; } public override int sort(ListBoxRow row1, ListBoxRow row2) { ListRow c1 = (row1 as ListRow); ListRow c2 = (row2 as ListRow); return c1.name_label.label.collate(c2.name_label.label); } } } dino-0.1.0/main/src/ui/add_conversation/select_contact_dialog.vala0000644000000000000000000000723213614354364024004 0ustar rootrootusing Gee; using Gdk; using Gtk; using Dino.Entities; using Xmpp; namespace Dino.Ui { public class SelectContactDialog : Gtk.Dialog { public signal void selected(Account account, Jid jid); public Button ok_button; private RosterList roster_list; private SelectJidFragment select_jid_fragment; private StreamInteractor stream_interactor; private Gee.List accounts; public SelectContactDialog(StreamInteractor stream_interactor, Gee.List accounts) { Object(use_header_bar : Util.use_csd() ? 1 : 0); modal = true; this.stream_interactor = stream_interactor; this.accounts = accounts; setup_view(); setup_headerbar(); } public void set_filter(string str) { select_jid_fragment.set_filter(str); } private void setup_headerbar() { Button cancel_button = new Button(); cancel_button.set_label(_("Cancel")); cancel_button.visible = true; ok_button = new Button(); ok_button.get_style_context().add_class("suggested-action"); ok_button.sensitive = false; ok_button.visible = true; if (Util.use_csd()) { HeaderBar header_bar = get_header_bar() as HeaderBar; header_bar.show_close_button = false; header_bar.pack_start(cancel_button); header_bar.pack_end(ok_button); } else { Box box = new Box(Orientation.HORIZONTAL, 5) { halign=Align.END, margin_bottom=15, margin_start=80, margin_end=80, visible=true }; cancel_button.halign = Align.START; ok_button.halign = Align.END; box.add(cancel_button); box.add(ok_button); get_content_area().add(box); } cancel_button.clicked.connect(() => { close(); }); ok_button.clicked.connect(() => { ListRow? selected_row = roster_list.get_selected_row() as ListRow; if (selected_row != null) selected(selected_row.account, selected_row.jid); close(); }); } private void setup_view() { roster_list = new RosterList(stream_interactor, accounts); roster_list.row_activated.connect(() => { ok_button.clicked(); }); select_jid_fragment = new SelectJidFragment(stream_interactor, roster_list, accounts); select_jid_fragment.add_jid.connect((row) => { AddContactDialog add_contact_dialog = new AddContactDialog(stream_interactor); add_contact_dialog.set_transient_for(this); add_contact_dialog.present(); }); select_jid_fragment.remove_jid.connect((row) => { ListRow list_row = roster_list.get_selected_row() as ListRow; stream_interactor.get_module(RosterManager.IDENTITY).remove_jid(list_row.account, list_row.jid); }); select_jid_fragment.notify["done"].connect(() => { ok_button.sensitive = select_jid_fragment.done; }); get_content_area().add(select_jid_fragment); } } public class AddChatDialog : SelectContactDialog { public signal void added(Conversation conversation); public AddChatDialog(StreamInteractor stream_interactor, Gee.List accounts) { base(stream_interactor, accounts); title = _("Start Conversation"); ok_button.label = _("Start"); selected.connect((account, jid) => { Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(jid, account, Conversation.Type.CHAT); stream_interactor.get_module(ConversationManager.IDENTITY).start_conversation(conversation); added(conversation); }); } } } dino-0.1.0/main/src/ui/add_conversation/select_jid_fragment.vala0000644000000000000000000000736713614354364023474 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; using Xmpp; namespace Dino.Ui { [GtkTemplate (ui = "/im/dino/Dino/add_conversation/select_jid_fragment.ui")] public class SelectJidFragment : Gtk.Box { public signal void add_jid(); public signal void remove_jid(ListRow row); public bool done { get { return filterable_list.get_selected_row() != null; } private set {} } [GtkChild] private Entry entry; [GtkChild] private Box box; [GtkChild] private Button add_button; [GtkChild] private Button remove_button; private StreamInteractor stream_interactor; private FilterableList filterable_list; private Gee.List accounts; private ArrayList added_rows = new ArrayList(); public SelectJidFragment(StreamInteractor stream_interactor, FilterableList filterable_list, Gee.List accounts) { this.stream_interactor = stream_interactor; this.filterable_list = filterable_list; this.accounts = accounts; filterable_list.visible = true; filterable_list.activate_on_single_click = false; filterable_list.vexpand = true; box.add(filterable_list); filterable_list.set_sort_func(sort); filterable_list.row_selected.connect(check_buttons_active); filterable_list.row_selected.connect(() => { done = true; }); // just for notifying entry.changed.connect(() => { set_filter(entry.text); }); add_button.clicked.connect(() => { add_jid(); }); remove_button.clicked.connect(() => { remove_jid(filterable_list.get_selected_row() as ListRow); }); } public void set_filter(string str) { if (entry.text != str) entry.text = str; foreach (AddListRow row in added_rows) row.destroy(); added_rows.clear(); string[] ? values = str == "" ? null : str.split(" "); filterable_list.set_filter_values(values); try { Jid parsed_jid = new Jid(str); if (parsed_jid != null && parsed_jid.localpart != null) { foreach (Account account in accounts) { AddListRow row = new AddListRow(stream_interactor, parsed_jid, account); filterable_list.add(row); added_rows.add(row); } } } catch (InvalidJidError ignored) { // Ignore } } private void check_buttons_active() { ListBoxRow? row = filterable_list.get_selected_row(); bool active = row != null && !row.get_type().is_a(typeof(AddListRow)); remove_button.sensitive = active; } private int sort(ListBoxRow row1, ListBoxRow row2) { AddListRow al1 = (row1 as AddListRow); AddListRow al2 = (row2 as AddListRow); if (al1 != null && al2 == null) { return -1; } else if (al2 != null && al1 == null) { return 1; } return filterable_list.sort(row1, row2); } private class AddListRow : ListRow { public AddListRow(StreamInteractor stream_interactor, Jid jid, Account account) { this.account = account; this.jid = jid; name_label.label = jid.to_string(); if (stream_interactor.get_accounts().size > 1) { via_label.label = account.bare_jid.to_string(); } else { via_label.visible = false; } image.set_text("?"); } } } public abstract class FilterableList : Gtk.ListBox { public string[]? filter_values; public void set_filter_values(string[] values) { if (filter_values == values) return; filter_values = values; invalidate_filter(); } public abstract int sort(ListBoxRow row1, ListBoxRow row2); } } dino-0.1.0/main/src/ui/application.vala0000644000000000000000000002361013614354364016452 0ustar rootrootusing Gtk; using Dino.Entities; using Dino.Ui; using Xmpp; public class Dino.Ui.Application : Gtk.Application, Dino.Application { private Notifications notifications; private UnifiedWindow window; public UnifiedWindowController controller; public Database db { get; set; } public Dino.Entities.Settings settings { get; set; } private Config config { get; set; } public StreamInteractor stream_interactor { get; set; } public Plugins.Registry plugin_registry { get; set; default = new Plugins.Registry(); } public SearchPathGenerator? search_path_generator { get; set; } public Application() throws Error { Object(application_id: "im.dino.Dino", flags: ApplicationFlags.HANDLES_OPEN); init(); Environment.set_application_name("Dino"); Window.set_default_icon_name("im.dino.Dino"); CssProvider provider = new CssProvider(); provider.load_from_resource("/im/dino/Dino/theme.css"); StyleContext.add_provider_for_screen(Gdk.Screen.get_default(), provider, STYLE_PROVIDER_PRIORITY_APPLICATION); create_actions(); startup.connect(() => { notifications = new Notifications(stream_interactor); notifications.start(); }); activate.connect(() => { if (window == null) { controller = new UnifiedWindowController(this, stream_interactor, db); config = new Config(db); window = new UnifiedWindow(this, stream_interactor, db, config); controller.set_window(window); if ((get_flags() & ApplicationFlags.IS_SERVICE) == ApplicationFlags.IS_SERVICE) window.delete_event.connect(window.hide_on_delete); notifications.conversation_selected.connect((conversation) => controller.select_conversation(conversation)); } window.present(); }); } public void handle_uri(string jid, string query, Gee.Map options) { switch (query) { case "join": show_join_muc_dialog(null, jid); break; case "message": Gee.List accounts = stream_interactor.get_accounts(); Jid parsed_jid = null; try { parsed_jid = new Jid(jid); } catch (InvalidJidError ignored) { // Ignored } if (accounts.size == 1 && parsed_jid != null) { Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(parsed_jid, accounts[0], Conversation.Type.CHAT); stream_interactor.get_module(ConversationManager.IDENTITY).start_conversation(conversation); controller.select_conversation(conversation); } else { AddChatDialog dialog = new AddChatDialog(stream_interactor, stream_interactor.get_accounts()); dialog.set_filter(jid); dialog.set_transient_for(window); dialog.added.connect((conversation) => { controller.select_conversation(conversation); }); dialog.present(); } break; } } private void create_actions() { SimpleAction accounts_action = new SimpleAction("accounts", null); accounts_action.activate.connect(show_accounts_window); add_action(accounts_action); SimpleAction settings_action = new SimpleAction("settings", null); settings_action.activate.connect(show_settings_window); add_action(settings_action); SimpleAction about_action = new SimpleAction("about", null); about_action.activate.connect(show_about_window); add_action(about_action); SimpleAction quit_action = new SimpleAction("quit", null); quit_action.activate.connect(quit); add_action(quit_action); set_accels_for_action("app.quit", new string[]{"Q"}); SimpleAction open_conversation_action = new SimpleAction("open-conversation", VariantType.INT32); open_conversation_action.activate.connect((variant) => { Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_by_id(variant.get_int32()); if (conversation != null) controller.select_conversation(conversation); window.present(); }); add_action(open_conversation_action); SimpleAction deny_subscription_action = new SimpleAction("deny-subscription", VariantType.INT32); deny_subscription_action.activate.connect((variant) => { Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_by_id(variant.get_int32()); if (conversation == null) return; stream_interactor.get_module(PresenceManager.IDENTITY).deny_subscription(conversation.account, conversation.counterpart); }); add_action(deny_subscription_action); SimpleAction contacts_action = new SimpleAction("add_chat", null); contacts_action.activate.connect(() => { AddChatDialog add_chat_dialog = new AddChatDialog(stream_interactor, stream_interactor.get_accounts()); add_chat_dialog.set_transient_for(window); add_chat_dialog.added.connect((conversation) => controller.select_conversation(conversation)); add_chat_dialog.present(); }); add_action(contacts_action); set_accels_for_action("app.add_chat", new string[]{"T"}); SimpleAction conference_action = new SimpleAction("add_conference", null); conference_action.activate.connect(() => { AddConferenceDialog add_conference_dialog = new AddConferenceDialog(stream_interactor); add_conference_dialog.set_transient_for(window); add_conference_dialog.present(); }); add_action(conference_action); set_accels_for_action("app.add_conference", new string[]{"G"}); SimpleAction accept_muc_invite_action = new SimpleAction("open-muc-join", VariantType.INT32); accept_muc_invite_action.activate.connect((variant) => { Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_by_id(variant.get_int32()); if (conversation == null) return; show_join_muc_dialog(conversation.account, conversation.counterpart.to_string()); }); add_action(accept_muc_invite_action); SimpleAction loop_conversations_action = new SimpleAction("loop_conversations", null); loop_conversations_action.activate.connect(() => { window.loop_conversations(false); }); add_action(loop_conversations_action); set_accels_for_action("app.loop_conversations", new string[]{"Tab"}); SimpleAction loop_conversations_bw_action = new SimpleAction("loop_conversations_bw", null); loop_conversations_bw_action.activate.connect(() => { window.loop_conversations(true); }); add_action(loop_conversations_bw_action); set_accels_for_action("app.loop_conversations_bw", new string[]{"Tab"}); SimpleAction open_shortcuts_action = new SimpleAction("open_shortcuts", null); open_shortcuts_action.activate.connect((variant) => { Builder builder = new Builder.from_resource("/im/dino/Dino/shortcuts.ui"); var dialog = (ShortcutsWindow) builder.get_object("shortcuts-window"); dialog.set_transient_for(get_active_window()); dialog.present(); }); add_action(open_shortcuts_action); } public bool use_csd() { return Environment.get_variable("GTK_CSD") != "0"; } private void show_accounts_window() { ManageAccounts.Dialog dialog = new ManageAccounts.Dialog(stream_interactor, db); dialog.set_transient_for(get_active_window()); dialog.account_enabled.connect(add_connection); dialog.account_disabled.connect(remove_connection); dialog.present(); } private void show_settings_window() { SettingsDialog dialog = new SettingsDialog(); dialog.set_transient_for(get_active_window()); dialog.present(); } private void show_about_window() { show_about_dialog(get_active_window(), logo_icon_name: "im.dino.Dino", program_name: "Dino", version: Dino.VERSION.strip().length == 0 ? null : Dino.VERSION, comments: "Dino. Communicating happiness.", website: "https://dino.im/", website_label: "dino.im", copyright: "Copyright ยฉ 2016-2019 - Dino Team", license_type: License.GPL_3_0); } private void show_join_muc_dialog(Account? account, string jid) { Dialog dialog = new Dialog.with_buttons(_("Join Channel"), window, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.USE_HEADER_BAR, _("Join"), ResponseType.OK, _("Cancel"), ResponseType.CANCEL); dialog.modal = true; Button ok_button = dialog.get_widget_for_response(ResponseType.OK) as Button; ok_button.get_style_context().add_class("suggested-action"); ConferenceDetailsFragment conference_fragment = new ConferenceDetailsFragment(stream_interactor) { ok_button=ok_button }; conference_fragment.jid = jid; if (account != null) { conference_fragment.account = account; } Box content_area = dialog.get_content_area(); content_area.add(conference_fragment); conference_fragment.joined.connect(() => { dialog.destroy(); }); dialog.response.connect((response_id) => { if (response_id == ResponseType.CANCEL) { dialog.destroy(); } }); dialog.present(); } } dino-0.1.0/main/src/ui/avatar_drawer.vala0000644000000000000000000001752113614354364016775 0ustar rootrootusing Cairo; using Gee; using Gdk; using Gtk; using Xmpp.Util; namespace Dino.Ui { public class AvatarDrawer { public const string GRAY = "555753"; private Gee.List tiles = new ArrayList(); private int height = 35; private int width = 35; private bool gray; private int base_factor = 1; private string font_family = "Sans"; public AvatarDrawer size(int height, int width = height) { this.height = height; this.width = width; return this; } public AvatarDrawer grayscale() { this.gray = true; return this; } public AvatarDrawer tile(Pixbuf? image, string? name, string? hex_color) { tiles.add(new AvatarTile(image, name, hex_color)); return this; } public AvatarDrawer plus() { tiles.add(new AvatarTile(null, "โ€ฆ", GRAY)); return this; } public AvatarDrawer scale(int base_factor) { this.base_factor = base_factor; return this; } public AvatarDrawer font(string font_family) { this.font_family = font_family; return this; } public ImageSurface draw_image_surface() { ImageSurface surface = new ImageSurface(Format.ARGB32, width, height); draw_on_context(new Context(surface)); return surface; } public void draw_on_context(Cairo.Context ctx) { double radius = 3 * base_factor; double degrees = Math.PI / 180.0; ctx.new_sub_path(); ctx.arc(width - radius, radius, radius, -90 * degrees, 0 * degrees); ctx.arc(width - radius, height - radius, radius, 0 * degrees, 90 * degrees); ctx.arc(radius, height - radius, radius, 90 * degrees, 180 * degrees); ctx.arc(radius, radius, radius, 180 * degrees, 270 * degrees); ctx.close_path(); ctx.clip(); if (this.tiles.size == 4) { Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height); Cairo.Context bufctx = new Cairo.Context(buffer); bufctx.scale(0.5, 0.5); bufctx.set_source_surface(sub_surface_idx(ctx, 0, width - 1, height - 1, 2 * base_factor), 0, 0); bufctx.paint(); bufctx.set_source_surface(sub_surface_idx(ctx, 1, width - 1, height - 1, 2 * base_factor), width + 1, 0); bufctx.paint(); bufctx.set_source_surface(sub_surface_idx(ctx, 2, width - 1, height - 1, 2 * base_factor), 0, height + 1); bufctx.paint(); bufctx.set_source_surface(sub_surface_idx(ctx, 3, width - 1, height - 1, 2 * base_factor), width + 1, height + 1); bufctx.paint(); ctx.set_source_surface(buffer, 0, 0); ctx.paint(); } else if (this.tiles.size == 3) { Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height); Cairo.Context bufctx = new Cairo.Context(buffer); bufctx.scale(0.5, 0.5); bufctx.set_source_surface(sub_surface_idx(ctx, 0, width - 1, height - 1, 2 * base_factor), 0, 0); bufctx.paint(); bufctx.set_source_surface(sub_surface_idx(ctx, 1, width - 1, height * 2, 2 * base_factor), width + 1, 0); bufctx.paint(); bufctx.set_source_surface(sub_surface_idx(ctx, 2, width - 1, height - 1, 2 * base_factor), 0, height + 1); bufctx.paint(); ctx.set_source_surface(buffer, 0, 0); ctx.paint(); } else if (this.tiles.size == 2) { Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height); Cairo.Context bufctx = new Cairo.Context(buffer); bufctx.scale(0.5, 0.5); bufctx.set_source_surface(sub_surface_idx(ctx, 0, width - 1, height * 2, 2 * base_factor), 0, 0); bufctx.paint(); bufctx.set_source_surface(sub_surface_idx(ctx, 1, width - 1, height * 2, 2 * base_factor), width + 1, 0); bufctx.paint(); ctx.set_source_surface(buffer, 0, 0); ctx.paint(); } else if (this.tiles.size == 1) { ctx.set_source_surface(sub_surface_idx(ctx, 0, width, height, base_factor), 0, 0); ctx.paint(); } else if (this.tiles.size == 0) { ctx.set_source_surface(sub_surface_idx(ctx, -1, width, height, base_factor), 0, 0); ctx.paint(); } if (gray) { // convert to greyscale ctx.set_operator(Cairo.Operator.HSL_COLOR); ctx.set_source_rgb(1, 1, 1); ctx.rectangle(0, 0, width, height); ctx.fill(); // make the visible part more light ctx.set_operator(Cairo.Operator.ATOP); ctx.set_source_rgba(1, 1, 1, 0.7); ctx.rectangle(0, 0, width, height); ctx.fill(); } } private Cairo.Surface sub_surface_idx(Cairo.Context ctx, int idx, int width, int height, int font_factor = 1) { Gdk.Pixbuf? avatar = idx >= 0 ? tiles[idx].image : null; string? name = idx >= 0 ? tiles[idx].name : ""; string hex_color = !gray && idx >= 0 ? tiles[idx].hex_color : GRAY; return sub_surface(ctx, font_family, avatar, name, hex_color, width, height, font_factor); } private static Cairo.Surface sub_surface(Cairo.Context ctx, string font_family, Gdk.Pixbuf? avatar, string? name, string? hex_color, int width, int height, int font_factor = 1) { Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height); Cairo.Context bufctx = new Cairo.Context(buffer); if (avatar == null) { set_source_hex_color(bufctx, hex_color ?? GRAY); bufctx.rectangle(0, 0, width, height); bufctx.fill(); string text = name == null ? "โ€ฆ" : name.get_char(0).toupper().to_string(); bufctx.select_font_face(font_family, Cairo.FontSlant.NORMAL, Cairo.FontWeight.NORMAL); bufctx.set_font_size(width / font_factor < 40 ? font_factor * 17 : font_factor * 25); Cairo.TextExtents extents; bufctx.text_extents(text, out extents); double x_pos = width/2 - (extents.width/2 + extents.x_bearing); double y_pos = height/2 - (extents.height/2 + extents.y_bearing); bufctx.move_to(x_pos, y_pos); bufctx.set_source_rgba(1, 1, 1, 1); bufctx.show_text(text); } else { double w_scale = (double) width / avatar.width; double h_scale = (double) height / avatar.height; double scale = double.max(w_scale, h_scale); bufctx.scale(scale, scale); double x_off = 0, y_off = 0; if (scale == h_scale) { x_off = (width / scale - avatar.width) / 2.0; } else { y_off = (height / scale - avatar.height) / 2.0; } Gdk.cairo_set_source_pixbuf(bufctx, avatar, x_off, y_off); bufctx.get_source().set_filter(Cairo.Filter.BEST); bufctx.paint(); } return buffer; } private static void set_source_hex_color(Cairo.Context ctx, string hex_color) { ctx.set_source_rgba((double) from_hex(hex_color.substring(0, 2)) / 255, (double) from_hex(hex_color.substring(2, 2)) / 255, (double) from_hex(hex_color.substring(4, 2)) / 255, hex_color.length > 6 ? (double) from_hex(hex_color.substring(6, 2)) / 255 : 1); } } private class AvatarTile { public Pixbuf? image { get; private set; } public string? name { get; private set; } public string? hex_color { get; private set; } public AvatarTile(Pixbuf? image, string? name, string? hex_color) { this.image = image; this.name = name; this.hex_color = hex_color; } } }dino-0.1.0/main/src/ui/avatar_generator.vala0000644000000000000000000000000013614354364017457 0ustar rootrootdino-0.1.0/main/src/ui/avatar_image.vala0000644000000000000000000001640113614354364016567 0ustar rootrootusing Gtk; using Dino.Entities; using Xmpp; using Xmpp.Util; namespace Dino.Ui { public class AvatarImage : Misc { public int height { get; set; default = 35; } public int width { get; set; default = 35; } public bool allow_gray { get; set; default = true; } public StreamInteractor? stream_interactor { get; set; } public AvatarManager? avatar_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(AvatarManager.IDENTITY); } } public MucManager? muc_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(MucManager.IDENTITY); } } public PresenceManager? presence_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(PresenceManager.IDENTITY); } } public ConnectionManager? connection_manager { owned get { return stream_interactor == null ? null : stream_interactor.connection_manager; } } public Account account { get { return conversation.account; } } private AvatarDrawer? drawer; private Conversation conversation; private Jid[] jids; private Cairo.ImageSurface? cached_surface; private static int8 use_image_surface = -1; public AvatarImage() { can_focus = false; get_style_context().add_class("avatar"); } public override void get_preferred_width(out int minimum_width, out int natural_width) { minimum_width = width; natural_width = width; } public override void get_preferred_height(out int minimum_height, out int natural_height) { minimum_height = height; natural_height = height; } public override bool draw(Cairo.Context ctx_in) { if (drawer == null) return false; Cairo.Context ctx = ctx_in; int width = this.width, height = this.height, base_factor = 1; if (use_image_surface == -1) { // TODO: detect if we have to buffer in image surface use_image_surface = 1; } if (use_image_surface == 1) { ctx_in.scale(1f/scale_factor, 1f/scale_factor); if (cached_surface != null) { ctx_in.set_source_surface(cached_surface, 0, 0); ctx_in.paint(); return true; } width *= scale_factor; height *= scale_factor; base_factor *= scale_factor; cached_surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, width, height); ctx = new Cairo.Context(cached_surface); } drawer.size(height, width) .scale(base_factor) .font(get_pango_context().get_font_description().get_family()) .draw_on_context(ctx); if (use_image_surface == 1) { ctx_in.set_source_surface(ctx.get_target(), 0, 0); ctx_in.paint(); } return true; } public override void destroy() { disconnect_stream_interactor(); } private void disconnect_stream_interactor() { if (stream_interactor != null) { presence_manager.show_received.disconnect(on_show_received); avatar_manager.received_avatar.disconnect(on_received_avatar); stream_interactor.connection_manager.connection_state_changed.disconnect(on_connection_changed); stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.disconnect(on_roster_updated); muc_manager.private_room_occupant_updated.disconnect(on_private_room_occupant_updated); stream_interactor = null; } } private void on_show_received(Show show, Jid jid, Account account) { if (!account.equals(this.account)) return; update_avatar_if_jid(jid); } private void on_received_avatar(Gdk.Pixbuf avatar, Jid jid, Account account) { if (!account.equals(this.account)) return; update_avatar_if_jid(jid); } private void update_avatar_if_jid(Jid jid) { if (jid.equals_bare(this.conversation.counterpart)) { update_avatar_async.begin(); return; } foreach (Jid ours in this.jids) { if (jid.equals_bare(ours)) { update_avatar_async.begin(); return; } } } private void on_connection_changed(Account account, ConnectionManager.ConnectionState state) { if (!account.equals(this.account)) return; update_avatar_async.begin(); } private void on_roster_updated(Account account, Jid jid, Roster.Item roster_item) { if (!account.equals(this.account)) return; update_avatar_if_jid(jid); } private void on_private_room_occupant_updated(Account account, Jid room, Jid occupant) { if (!account.equals(this.account)) return; update_avatar_if_jid(room); } private bool is_self_online() { if (connection_manager != null) { return connection_manager.get_state(account) == ConnectionManager.ConnectionState.CONNECTED; } return false; } private bool is_counterpart_online() { return presence_manager.get_full_jids(conversation.counterpart, account) != null; } public void set_conversation(StreamInteractor stream_interactor, Conversation conversation) { set_avatar_async.begin(stream_interactor, conversation, new Jid[0]); } public void set_conversation_participant(StreamInteractor stream_interactor, Conversation conversation, Jid sub_jid) { set_avatar_async.begin(stream_interactor, conversation, new Jid[] {sub_jid}); } public void set_conversation_participants(StreamInteractor stream_interactor, Conversation conversation, Jid[] sub_jids) { set_avatar_async.begin(stream_interactor, conversation, sub_jids); } private async void update_avatar_async() { this.cached_surface = null; this.drawer = yield Util.get_conversation_participants_avatar_drawer(stream_interactor, conversation, jids); if (allow_gray && (!is_self_online() || !is_counterpart_online())) drawer.grayscale(); queue_draw(); } private async void set_avatar_async(StreamInteractor stream_interactor, Conversation conversation, Jid[] jids) { if (this.stream_interactor != null && stream_interactor != this.stream_interactor) { disconnect_stream_interactor(); } if (this.stream_interactor != stream_interactor) { this.stream_interactor = stream_interactor; presence_manager.show_received.connect(on_show_received); stream_interactor.get_module(AvatarManager.IDENTITY).received_avatar.connect(on_received_avatar); stream_interactor.connection_manager.connection_state_changed.connect(on_connection_changed); stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.connect(on_roster_updated); muc_manager.private_room_occupant_updated.connect(on_private_room_occupant_updated); } this.cached_surface = null; this.conversation = conversation; this.jids = jids; yield update_avatar_async(); } public void set_text(string text, bool gray = true) { disconnect_stream_interactor(); this.drawer = new AvatarDrawer().tile(null, text, null); if (gray) drawer.grayscale(); queue_draw(); } } } dino-0.1.0/main/src/ui/chat_input/0000755000000000000000000000000013614354364015436 5ustar rootrootdino-0.1.0/main/src/ui/chat_input/edit_history.vala0000644000000000000000000000527513614354364021022 0ustar rootrootusing Gdk; using Gee; using Gtk; using Dino.Entities; namespace Dino.Ui.ChatInput { public class EditHistory { private Conversation? conversation; private TextView text_input; private HashMap> histories = new HashMap>(Conversation.hash_func, Conversation.equals_func); private HashMap indices = new HashMap(Conversation.hash_func, Conversation.equals_func); public EditHistory(TextView text_input, GLib.Application application) { this.text_input = text_input; text_input.key_press_event.connect(on_text_input_key_press); text_input.cut_clipboard.connect_after(save_state); text_input.paste_clipboard.connect_after(save_state); text_input.move_cursor.connect_after(save_state); text_input.button_release_event.connect_after(() => { save_state(); return false; }); } public void initialize_for_conversation(Conversation conversation) { this.conversation = conversation; if (!histories.has_key(conversation)) { reset_history(); } } public bool on_text_input_key_press(EventKey event) { bool ctrl_pressed = (event.state & ModifierType.CONTROL_MASK) > 0; if (ctrl_pressed && event.keyval == Key.z) { undo(); } else if (ctrl_pressed && (event.keyval in new uint[]{ Key.Z, Key.y } )) { redo(); } else if (event.keyval in new uint[]{ Key.space, Key.Tab, Key.ISO_Left_Tab }) { save_state(); } return false; } private void undo() { save_state(); if (indices[conversation] > 0) { indices[conversation] = indices[conversation] - 1; text_input.buffer.text = histories[conversation][indices[conversation]]; } } private void redo() { if (indices[conversation] < histories[conversation].size - 1) { indices[conversation] = indices[conversation] + 1; text_input.buffer.text = histories[conversation][indices[conversation]]; } } private void save_state() { if (histories[conversation][indices[conversation]] == text_input.buffer.text) return; if (indices[conversation] < histories[conversation].size - 1) { histories[conversation] = histories[conversation].slice(0, indices[conversation] + 1); } histories[conversation].add(text_input.buffer.text); indices[conversation] = indices[conversation] + 1; } public void reset_history() { histories[conversation] = new ArrayList(); histories[conversation].add(""); indices[conversation] = 0; } } } dino-0.1.0/main/src/ui/chat_input/encryption_button.vala0000644000000000000000000000656113614354364022100 0ustar rootrootusing Gtk; using Gee; using Dino.Entities; namespace Dino.Ui { public class EncryptionButton : MenuButton { public signal void encryption_changed(Plugins.EncryptionListEntry? encryption_entry); private Conversation? conversation; private RadioButton? button_unencrypted; private Map encryption_radios = new HashMap(); private string? current_icon; private StreamInteractor stream_interactor; public EncryptionButton(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; use_popover = true; image = new Image.from_icon_name("changes-allow-symbolic", IconSize.BUTTON); get_style_context().add_class("flat"); Builder builder = new Builder.from_resource("/im/dino/Dino/menu_encryption.ui"); popover = builder.get_object("menu_encryption") as PopoverMenu; Box encryption_box = builder.get_object("encryption_box") as Box; button_unencrypted = builder.get_object("button_unencrypted") as RadioButton; button_unencrypted.toggled.connect(encryption_button_toggled); Application app = GLib.Application.get_default() as Application; foreach (var e in app.plugin_registry.encryption_list_entries) { RadioButton btn = new RadioButton.with_label(button_unencrypted.get_group(), e.name); encryption_radios[btn] = e; btn.toggled.connect(encryption_button_toggled); btn.visible = true; encryption_box.pack_end(btn, false); } clicked.connect(update_encryption_menu_state); } private void encryption_button_toggled() { foreach (RadioButton e in encryption_radios.keys) { if (e.get_active()) { conversation.encryption = encryption_radios[e].encryption; encryption_changed(encryption_radios[e]); update_encryption_menu_icon(); return; } } // Selected unencrypted conversation.encryption = Encryption.NONE; update_encryption_menu_icon(); encryption_changed(null); } private void update_encryption_menu_state() { foreach (RadioButton e in encryption_radios.keys) { if (conversation.encryption == encryption_radios[e].encryption) { e.set_active(true); encryption_changed(encryption_radios[e]); } } if (conversation.encryption == Encryption.NONE) { button_unencrypted.set_active(true); encryption_changed(null); } } private void set_icon(string icon) { if (icon != current_icon) { image = new Image.from_icon_name(icon, IconSize.BUTTON); current_icon = icon; } } private void update_encryption_menu_icon() { set_icon(conversation.encryption == Encryption.NONE ? "changes-allow-symbolic" : "changes-prevent-symbolic"); } public new void set_conversation(Conversation conversation) { this.conversation = conversation; update_encryption_menu_state(); update_encryption_menu_icon(); visible = !stream_interactor.get_module(MucManager.IDENTITY).is_public_room(conversation.account, conversation.counterpart) || conversation.encryption != Encryption.NONE; } } } dino-0.1.0/main/src/ui/chat_input/occupants_tab_completer.vala0000644000000000000000000001273513614354364023212 0ustar rootrootusing Gdk; using Gee; using Gtk; using Dino.Entities; using Xmpp; namespace Dino.Ui.ChatInput { /** * - With given prefix: Complete from occupant list (sorted lexicographically) * - W/o prefix: Complete from received messages (most recent first) * - At the start (with ",") and in the middle of a text * - Backwards tabbing */ public class OccupantsTabCompletor { private StreamInteractor stream_interactor; private Conversation? conversation; private TextView text_input; private Gee.List completions = new ArrayList(); private bool active = false; private int index = -1; public OccupantsTabCompletor(StreamInteractor stream_interactor, TextView text_input) { this.stream_interactor = stream_interactor; this.text_input = text_input; text_input.key_press_event.connect(on_text_input_key_press); } public void initialize_for_conversation(Conversation conversation) { this.conversation = conversation; } public bool on_text_input_key_press(EventKey event) { if (conversation.type_ == Conversation.Type.GROUPCHAT) { if (event.keyval == Key.Tab || event.keyval == Key.ISO_Left_Tab) { string text = text_input.buffer.text; int start_index = int.max(text.last_index_of(" "), text.last_index_of("\n")) + 1; string word = text.substring(start_index); if (!active) { if (word == "") { completions = generate_completions_from_messages(); } else { completions = generate_completions_from_occupants(word); } if (completions.size > 0) { active = true; index = -1; } } if (event.keyval != Key.ISO_Group_Shift && active) { text_input.buffer.text = next_completion(event.keyval == Key.ISO_Left_Tab); return true; } } else if (event.keyval != Key.Shift_L && active) { active = false; } } return false; } private string next_completion(bool backwards) { string text = text_input.buffer.text; int start_index = int.max(text.last_index_of(" "), text.last_index_of("\n")) + 1; string prev_completion = text.substring(start_index); if (index > -1) { start_index = int.max( text.last_index_of(completions[index]), text.substring(0, text.length - 1).last_index_of("\n") ); prev_completion = text.substring(start_index); } if (backwards) { index = int.max(index, 0) - 1; if (index < 0) index = completions.size - 1; } else { index = (index + 1) % (completions.size); } if (start_index == 0) { return completions[index] + ", "; } else { return text.substring(0, text.length - prev_completion.length) + completions[index] + " "; } } private Gee.List generate_completions_from_messages() { Gee.List ret = new ArrayList(); Gee.List messages = stream_interactor.get_module(MessageStorage.IDENTITY).get_messages(conversation, 10); for (int i = messages.size - 1; i > 0; i--) { string resourcepart = messages[i].from.resourcepart; Jid? own_nick = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account); if (resourcepart != null && resourcepart != "" && (own_nick == null || resourcepart != own_nick.resourcepart) && !ret.contains(resourcepart)) { ret.add(resourcepart); } } return ret; } private Gee.List generate_completions_from_occupants(string prefix) { Gee.List ret = new ArrayList(); // First suggest nicks that have recently writen something Gee.List messages = stream_interactor.get_module(MessageStorage.IDENTITY).get_messages(conversation, 50); for (int i = messages.size - 1; i > 0; i--) { string resourcepart = messages[i].from.resourcepart; Jid? own_nick = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account); if (resourcepart != null && resourcepart != "" && resourcepart.to_string().down().has_prefix(prefix.down()) && (own_nick == null || resourcepart != own_nick.resourcepart) && !ret.contains(resourcepart)) { ret.add(resourcepart.to_string()); } } // Then, suggest other nicks in alphabetical order Gee.List? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_other_occupants(conversation.counterpart, conversation.account); Gee.List filtered_occupants = new ArrayList(); if (occupants != null) { foreach (Jid jid in occupants) { string resourcepart = jid.resourcepart.to_string(); if (resourcepart.down().has_prefix(prefix.down()) && !ret.contains(resourcepart)) { filtered_occupants.add(resourcepart); } } } filtered_occupants.sort(); ret.add_all(filtered_occupants); return ret; } } } dino-0.1.0/main/src/ui/chat_input/smiley_converter.vala0000644000000000000000000000466313614354364021705 0ustar rootrootusing Gdk; using Gee; using Gtk; using Dino.Entities; namespace Dino.Ui.ChatInput { class SmileyConverter { private TextView text_input; private static HashMap smiley_translations = new HashMap(); private Regex whitespace_regex = /\s/; static construct { smiley_translations[":)"] = "๐Ÿ™‚"; smiley_translations[":D"] = "๐Ÿ˜€"; smiley_translations[";)"] = "๐Ÿ˜‰"; smiley_translations["O:)"] = "๐Ÿ˜‡"; smiley_translations["O:-)"] = "๐Ÿ˜‡"; smiley_translations["]:>"] = "๐Ÿ˜ˆ"; smiley_translations[":o"] = "๐Ÿ˜ฎ"; smiley_translations[":P"] = "๐Ÿ˜›"; smiley_translations[";P"] = "๐Ÿ˜œ"; smiley_translations[":("] = "๐Ÿ™"; smiley_translations[":'("] = "๐Ÿ˜ข"; smiley_translations[":/"] = "๐Ÿ˜•"; } public SmileyConverter(TextView text_input) { this.text_input = text_input; text_input.key_press_event.connect(on_text_input_key_press); } public bool on_text_input_key_press(EventKey event) { if (event.keyval == Key.space || event.keyval == Key.Return) { check_convert(); } return false; } private void check_convert() { if (!Dino.Application.get_default().settings.convert_utf8_smileys) return; TextIter? start_iter; text_input.buffer.get_start_iter(out start_iter); TextIter? cursor_iter; text_input.buffer.get_iter_at_mark(out cursor_iter, text_input.buffer.get_insert()); if (start_iter == null || cursor_iter == null) return; string text = text_input.buffer.get_text(start_iter, cursor_iter, true); foreach (string smiley in smiley_translations.keys) { if (text.has_suffix(smiley)) { if (text.length == smiley.length || whitespace_regex.match(text[text.length - smiley.length - 1].to_string())) { TextIter? end_iter; text_input.buffer.get_end_iter(out end_iter); if (end_iter == null) continue; TextIter smiley_start_iter = cursor_iter; smiley_start_iter.backward_chars(smiley.length); text_input.buffer.delete(ref smiley_start_iter, ref cursor_iter); text_input.buffer.insert_text(ref cursor_iter, smiley_translations[smiley], smiley_translations[smiley].length); } } } } } } dino-0.1.0/main/src/ui/chat_input/view.vala0000644000000000000000000001456113614354364017264 0ustar rootrootusing Gdk; using Gee; using Gtk; using Dino.Entities; using Xmpp; namespace Dino.Ui.ChatInput { [GtkTemplate (ui = "/im/dino/Dino/chat_input.ui")] public class View : Box { public signal void send_text(); public string text { owned get { return text_input.buffer.text; } set { text_input.buffer.text = value; } } private StreamInteractor stream_interactor; private Conversation? conversation; private HashMap entry_cache = new HashMap(Conversation.hash_func, Conversation.equals_func); private int vscrollbar_min_height; public OccupantsTabCompletor occupants_tab_completor; private SmileyConverter smiley_converter; public EditHistory edit_history; [GtkChild] public Frame frame; [GtkChild] public ScrolledWindow scrolled; [GtkChild] public TextView text_input; [GtkChild] public Box outer_box; [GtkChild] public Button file_button; [GtkChild] public Separator file_separator; [GtkChild] public Label chat_input_status; public EncryptionButton encryption_widget; public View init(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; occupants_tab_completor = new OccupantsTabCompletor(stream_interactor, text_input); smiley_converter = new SmileyConverter(text_input); edit_history = new EditHistory(text_input, GLib.Application.get_default()); encryption_widget = new EncryptionButton(stream_interactor) { relief=ReliefStyle.NONE, margin_top=3, valign=Align.START, visible=true }; file_button.clicked.connect(() => { PreviewFileChooserNative chooser = new PreviewFileChooserNative("Select file", get_toplevel() as Gtk.Window, FileChooserAction.OPEN, "Select", "Cancel"); if (chooser.run() == Gtk.ResponseType.ACCEPT) { string uri = chooser.get_filename(); stream_interactor.get_module(FileManager.IDENTITY).send_file.begin(uri, conversation); } }); file_button.get_style_context().add_class("dino-attach-button"); scrolled.get_vscrollbar().get_preferred_height(out vscrollbar_min_height, null); scrolled.vadjustment.notify["upper"].connect_after(on_upper_notify); encryption_widget.get_style_context().add_class("dino-chatinput-button"); encryption_widget.encryption_changed.connect(update_file_transfer_availability); // Emoji button for emoji picker (recents don't work < 3.22.19, category icons don't work <3.23.2) if (Gtk.get_major_version() >= 3 && Gtk.get_minor_version() >= 24) { MenuButton emoji_button = new MenuButton() { relief=ReliefStyle.NONE, margin_top=3, valign=Align.START, visible=true }; emoji_button.get_style_context().add_class("flat"); emoji_button.get_style_context().add_class("dino-chatinput-button"); emoji_button.image = new Image.from_icon_name("dino-emoticon-symbolic", IconSize.BUTTON) { visible=true }; EmojiChooser chooser = new EmojiChooser(); chooser.emoji_picked.connect((emoji) => { text_input.buffer.insert_at_cursor(emoji, emoji.data.length); }); emoji_button.set_popover(chooser); outer_box.add(emoji_button); } outer_box.add(encryption_widget); text_input.key_press_event.connect(on_text_input_key_press); Util.force_css(frame, "* { border-radius: 3px; }"); return this; } private void update_file_transfer_availability() { bool upload_available = stream_interactor.get_module(FileManager.IDENTITY).is_upload_available(conversation); file_button.visible = upload_available; file_separator.visible = upload_available; } public void initialize_for_conversation(Conversation conversation) { if (this.conversation != null) entry_cache[this.conversation] = text_input.buffer.text; this.conversation = conversation; update_file_transfer_availability(); text_input.buffer.text = ""; if (entry_cache.has_key(conversation)) { text_input.buffer.text = entry_cache[conversation]; } text_input.grab_focus(); } public void set_input_state(Plugins.InputFieldStatus.MessageType message_type) { switch (message_type) { case Plugins.InputFieldStatus.MessageType.NONE: this.get_style_context().remove_class("dino-input-warning"); this.get_style_context().remove_class("dino-input-error"); break; case Plugins.InputFieldStatus.MessageType.INFO: this.get_style_context().remove_class("dino-input-warning"); this.get_style_context().remove_class("dino-input-error"); break; case Plugins.InputFieldStatus.MessageType.WARNING: this.get_style_context().add_class("dino-input-warning"); this.get_style_context().remove_class("dino-input-error"); break; case Plugins.InputFieldStatus.MessageType.ERROR: this.get_style_context().remove_class("dino-input-warning"); this.get_style_context().add_class("dino-input-error"); break; } } public void highlight_state_description() { chat_input_status.get_style_context().add_class("input-status-highlight-once"); Timeout.add_seconds(1, () => { chat_input_status.get_style_context().remove_class("input-status-highlight-once"); return false; }); } private bool on_text_input_key_press(EventKey event) { if (event.keyval in new uint[]{Key.Return, Key.KP_Enter}) { if ((event.state & ModifierType.SHIFT_MASK) > 0) { text_input.buffer.insert_at_cursor("\n", 1); } else if (this.text != "") { send_text(); edit_history.reset_history(); } return true; } return false; } private void on_upper_notify() { scrolled.vadjustment.value = scrolled.vadjustment.upper - scrolled.vadjustment.page_size; // hack for vscrollbar not requiring space and making textview higher //TODO doesn't resize immediately scrolled.get_vscrollbar().visible = (scrolled.vadjustment.upper > scrolled.max_content_height - 2 * vscrollbar_min_height); } } } dino-0.1.0/main/src/ui/chat_input_controller.vala0000644000000000000000000001364113614354364020553 0ustar rootrootusing Gee; using Gdk; using Gtk; using Dino.Entities; namespace Dino.Ui { public class ChatInputController : Object { public new string? conversation_display_name { get; set; } public string? conversation_topic { get; set; } private Conversation? conversation; private ChatInput.View chat_input; private Label status_description_label; private StreamInteractor stream_interactor; private Plugins.InputFieldStatus input_field_status; public ChatInputController(ChatInput.View chat_input, StreamInteractor stream_interactor) { this.chat_input = chat_input; this.status_description_label = chat_input.chat_input_status; this.stream_interactor = stream_interactor; reset_input_field_status(); chat_input.text_input.buffer.changed.connect(on_text_input_changed); chat_input.send_text.connect(send_text); chat_input.encryption_widget.encryption_changed.connect(on_encryption_changed); stream_interactor.get_module(FileManager.IDENTITY).upload_available.connect(on_upload_available); } public void set_conversation(Conversation conversation) { this.conversation = conversation; reset_input_field_status(); chat_input.occupants_tab_completor.initialize_for_conversation(conversation); chat_input.edit_history.initialize_for_conversation(conversation); chat_input.encryption_widget.set_conversation(conversation); } private void on_encryption_changed(Plugins.EncryptionListEntry? encryption_entry) { reset_input_field_status(); if (encryption_entry == null) return; encryption_entry.encryption_activated(conversation, set_input_field_status); } private void set_input_field_status(Plugins.InputFieldStatus? status) { input_field_status = status; chat_input.set_input_state(status.message_type); status_description_label.label = status.message; chat_input.file_button.sensitive = status.input_state == Plugins.InputFieldStatus.InputState.NORMAL; } private void reset_input_field_status() { set_input_field_status(new Plugins.InputFieldStatus("", Plugins.InputFieldStatus.MessageType.NONE, Plugins.InputFieldStatus.InputState.NORMAL)); } private void on_upload_available(Account account) { if (conversation != null && conversation.account.equals(account)) { chat_input.file_button.visible = true; chat_input.file_separator.visible = true; } } private void send_text() { // Don't do anything if we're in a NO_SEND state. Don't clear the chat input, don't send. if (input_field_status.input_state == Plugins.InputFieldStatus.InputState.NO_SEND) { chat_input.highlight_state_description(); return; } string text = chat_input.text_input.buffer.text; chat_input.text_input.buffer.text = ""; if (text.has_prefix("/")) { string[] token = text.split(" ", 2); switch(token[0]) { case "/me": // Just send as is. break; case "/say": if (token.length == 1) return; text = token[1]; break; case "/kick": stream_interactor.get_module(MucManager.IDENTITY).kick(conversation.account, conversation.counterpart, token[1]); return; case "/affiliate": if (token.length > 1) { string[] user_role = token[1].split(" ", 2); if (user_role.length == 2) { stream_interactor.get_module(MucManager.IDENTITY).change_affiliation(conversation.account, conversation.counterpart, user_role[0].strip(), user_role[1].strip()); } } return; case "/nick": stream_interactor.get_module(MucManager.IDENTITY).change_nick(conversation.account, conversation.counterpart, token[1]); return; case "/ping": Xmpp.XmppStream? stream = stream_interactor.get_stream(conversation.account); try { stream.get_module(Xmpp.Xep.Ping.Module.IDENTITY).send_ping(stream, conversation.counterpart.with_resource(token[1]), null); } catch (Xmpp.InvalidJidError e) { warning("Could not ping invalid Jid: %s", e.message); } return; case "/topic": stream_interactor.get_module(MucManager.IDENTITY).change_subject(conversation.account, conversation.counterpart, token[1]); return; default: if (token[0].has_prefix("//")) { text = text.substring(1); } else { string cmd_name = token[0].substring(1); Dino.Application app = GLib.Application.get_default() as Dino.Application; if (app != null && app.plugin_registry.text_commands.has_key(cmd_name)) { string? new_text = app.plugin_registry.text_commands[cmd_name].handle_command(token[1], conversation); if (new_text == null) return; text = (!)new_text; } } break; } } stream_interactor.get_module(MessageProcessor.IDENTITY).send_text(text, conversation); } private void on_text_input_changed() { if (chat_input.text_input.buffer.text != "") { stream_interactor.get_module(ChatInteraction.IDENTITY).on_message_entered(conversation); } else { stream_interactor.get_module(ChatInteraction.IDENTITY).on_message_cleared(conversation); } } } } dino-0.1.0/main/src/ui/contact_details/0000755000000000000000000000000013614354364016440 5ustar rootrootdino-0.1.0/main/src/ui/contact_details/blocking_provider.vala0000644000000000000000000000277713614354364023024 0ustar rootrootusing Gtk; using Dino.Entities; namespace Dino.Ui.ContactDetails { public class BlockingProvider : Plugins.ContactDetailsProvider, Object { public string id { get { return "blocking"; } } private StreamInteractor stream_interactor; public BlockingProvider(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public void populate(Conversation conversation, Plugins.ContactDetails contact_details, Plugins.WidgetType type) { if (type != Plugins.WidgetType.GTK) return; if (conversation.type_ != Conversation.Type.CHAT) return; if (stream_interactor.get_module(BlockingManager.IDENTITY).is_supported(conversation.account)) { bool is_blocked = stream_interactor.get_module(BlockingManager.IDENTITY).is_blocked(conversation.account, conversation.counterpart); Switch sw = new Switch() { active=is_blocked, valign=Align.CENTER, visible=true }; sw.state_set.connect((state) => { if (state) { stream_interactor.get_module(BlockingManager.IDENTITY).block(conversation.account, conversation.counterpart); } else { stream_interactor.get_module(BlockingManager.IDENTITY).unblock(conversation.account, conversation.counterpart); } return false; }); contact_details.add(_("Settings"), _("Block"), _("Communication and status updates in either direction are blocked"), sw); } } } } dino-0.1.0/main/src/ui/contact_details/dialog.vala0000644000000000000000000001413413614354364020547 0ustar rootrootusing Gee; using Gtk; using Markup; using Pango; using Dino.Entities; namespace Dino.Ui.ContactDetails { [GtkTemplate (ui = "/im/dino/Dino/contact_details_dialog.ui")] public class Dialog : Gtk.Dialog { [GtkChild] public AvatarImage avatar; [GtkChild] public Util.EntryLabelHybrid name_hybrid; [GtkChild] public Label name_label; [GtkChild] public Label jid_label; [GtkChild] public Label account_label; [GtkChild] public Box main_box; private StreamInteractor stream_interactor; private Conversation conversation; private Plugins.ContactDetails contact_details = new Plugins.ContactDetails(); private HashMap categories = new HashMap(); private Util.LabelHybridGroup hybrid_group = new Util.LabelHybridGroup(); construct { name_hybrid.label.attributes = new AttrList(); name_hybrid.label.attributes.insert(attr_weight_new(Weight.BOLD)); } public Dialog(StreamInteractor stream_interactor, Conversation conversation) { Object(use_header_bar : Util.use_csd() ? 1 : 0); this.stream_interactor = stream_interactor; this.conversation = conversation; title = conversation.type_ == Conversation.Type.GROUPCHAT ? _("Conference Details") : _("Contact Details"); if (Util.use_csd()) { (get_header_bar() as HeaderBar).set_subtitle(Util.get_conversation_display_name(stream_interactor, conversation)); } setup_top(); contact_details.add.connect(add_entry); Application app = GLib.Application.get_default() as Application; app.plugin_registry.register_contact_details_entry(new SettingsProvider(stream_interactor)); app.plugin_registry.register_contact_details_entry(new BlockingProvider(stream_interactor)); app.plugin_registry.register_contact_details_entry(new MucConfigFormProvider(stream_interactor)); foreach (Plugins.ContactDetailsProvider provider in app.plugin_registry.contact_details_entries) { provider.populate(conversation, contact_details, Plugins.WidgetType.GTK); } destroy.connect(() => { contact_details.save(); }); } private void setup_top() { if (conversation.type_ == Conversation.Type.CHAT) { name_label.visible = false; jid_label.margin_start = new Button().get_style_context().get_padding(StateFlags.NORMAL).left + 1; name_hybrid.text = Util.get_conversation_display_name(stream_interactor, conversation); destroy.connect(() => { if (name_hybrid.text != Util.get_conversation_display_name(stream_interactor, conversation)) { stream_interactor.get_module(RosterManager.IDENTITY).set_jid_handle(conversation.account, conversation.counterpart, name_hybrid.text); } }); } else { name_hybrid.visible = false; name_label.label = Util.get_conversation_display_name(stream_interactor, conversation); } jid_label.label = conversation.counterpart.to_string(); account_label.label = "via " + conversation.account.bare_jid.to_string(); avatar.set_conversation(stream_interactor, conversation); } private void add_entry(string category, string label, string? description, Object wo) { if (!(wo is Widget)) return; Widget w = (Widget) wo; add_category(category); ListBoxRow list_row = new ListBoxRow() { activatable=false, visible=true }; Box row = new Box(Orientation.HORIZONTAL, 20) { margin_start=15, margin_end=15, margin_top=3, margin_bottom=3, visible=true }; list_row.add(row); Label label_label = new Label(label) { xalign=0, yalign=0.5f, hexpand=true, visible=true }; if (description != null && description != "") { Box box = new Box(Orientation.VERTICAL, 0) { visible=true }; box.add(label_label); Label desc_label = new Label("") { xalign=0, yalign=0.5f, hexpand=true, visible=true }; desc_label.set_markup("%s".printf(Markup.escape_text(description))); desc_label.get_style_context().add_class("dim-label"); box.add(desc_label); row.add(box); } else { row.add(label_label); } Widget widget = w; if (widget.get_type().is_a(typeof(Entry))) { Util.EntryLabelHybrid hybrid = new Util.EntryLabelHybrid.wrap(widget as Entry) { xalign=1, visible=true }; hybrid_group.add(hybrid); widget = hybrid; } else if (widget.get_type().is_a(typeof(ComboBoxText))) { Util.ComboBoxTextLabelHybrid hybrid = new Util.ComboBoxTextLabelHybrid.wrap(widget as ComboBoxText) { xalign=1, visible=true }; hybrid_group.add(hybrid); widget = hybrid; } widget.margin_bottom = 5; widget.margin_top = 5; row.add(widget); categories[category].add(list_row); int pref_height, pref_width; get_content_area().get_preferred_height(null, out pref_height); get_preferred_width(out pref_width, null); resize(pref_width, int.min(500, pref_height)); } private void add_category(string category) { if (!categories.has_key(category)) { ListBox list_box = new ListBox() { selection_mode=SelectionMode.NONE, visible=true }; categories[category] = list_box; list_box.set_header_func((row, before_row) => { if (row.get_header() == null && before_row != null) { row.set_header(new Separator(Orientation.HORIZONTAL)); } }); Box box = new Box(Orientation.VERTICAL, 5) { margin_top=12, margin_bottom=12, visible=true }; Label category_label = new Label("") { xalign=0, visible=true }; category_label.set_markup(@"$(Markup.escape_text(category))"); box.add(category_label); Frame frame = new Frame(null) { visible=true }; frame.add(list_box); box.add(frame); main_box.add(box); } } } } dino-0.1.0/main/src/ui/contact_details/muc_config_form_provider.vala0000644000000000000000000000762113614354364024361 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; using Xmpp.Xep; namespace Dino.Ui.ContactDetails { public class MucConfigFormProvider : Plugins.ContactDetailsProvider, Object { public string id { get { return "muc_config_form"; } } private StreamInteractor stream_interactor; public MucConfigFormProvider(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public void populate(Conversation conversation, Plugins.ContactDetails contact_details, Plugins.WidgetType type) { if (type != Plugins.WidgetType.GTK) return; if (conversation.type_ == Conversation.Type.GROUPCHAT) { Xmpp.XmppStream? stream = stream_interactor.get_stream(conversation.account); if (stream == null) return; stream_interactor.get_module(MucManager.IDENTITY).get_config_form.begin(conversation.account, conversation.counterpart, (_, res) => { DataForms.DataForm? data_form = stream_interactor.get_module(MucManager.IDENTITY).get_config_form.end(res); if (data_form == null) return; for (int i = 0; i < data_form.fields.size; i++) { DataForms.DataForm.Field field = data_form.fields[i]; add_field(field, contact_details); } string config_backup = data_form.stanza_node.to_string(); contact_details.save.connect(() => { // Only send the config form if something was changed if (config_backup != data_form.stanza_node.to_string()) { stream_interactor.get_module(MucManager.IDENTITY).set_config_form(conversation.account, conversation.counterpart, data_form); } }); }); } } public static void add_field(DataForms.DataForm.Field field, Plugins.ContactDetails contact_details) { string label = field.label ?? ""; string? desc = null; if (field.var != null) { switch (field.var) { case "muc#roomconfig_roomname": label = _("Name of the room"); break; case "muc#roomconfig_roomdesc": label = _("Description of the room"); break; case "muc#roomconfig_persistentroom": label = _("Persistent"); desc = _("The room will persist after the last occupant leaves"); break; case "muc#roomconfig_publicroom": label = _("Publicly searchable"); break; case "muc#roomconfig_changesubject": label = _("Occupants may change the subject"); break; case "muc#roomconfig_whois": label = _("Discover real JIDs"); desc = _("Who may discover real JIDs?"); break; case "muc#roomconfig_roomsecret": label = _("Password"); desc = _("Password required for room entry, if any"); break; case "muc#roomconfig_moderatedroom": label = _("Moderated"); desc = _("Only occupants with voice may send messages"); break; case "muc#roomconfig_membersonly": label = _("Members only"); desc = _("Only members may enter the room"); break; case "muc#roomconfig_historylength": label = _("Message history"); desc = _("Maximum amount of backlog issued by the room"); break; } } Widget? widget = Util.get_data_form_field_widget(field); if (widget != null) contact_details.add(_("Room Configuration"), label, desc, widget); } } } dino-0.1.0/main/src/ui/contact_details/settings_provider.vala0000644000000000000000000001350013614354364023056 0ustar rootrootusing Gtk; using Dino.Entities; namespace Dino.Ui.ContactDetails { public class SettingsProvider : Plugins.ContactDetailsProvider, Object { public string id { get { return "chat_settings"; } } private StreamInteractor stream_interactor; private string DETAILS_HEADLINE_CHAT = _("Settings"); private string DETAILS_HEADLINE_ROOM = _("Local Settings"); public SettingsProvider(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public void populate(Conversation conversation, Plugins.ContactDetails contact_details, Plugins.WidgetType type) { if (type != Plugins.WidgetType.GTK) return; if (!stream_interactor.get().is_public_room(conversation.account, conversation.counterpart)) { string details_headline = conversation.type_ == Conversation.Type.GROUPCHAT ? DETAILS_HEADLINE_ROOM : DETAILS_HEADLINE_CHAT; ComboBoxText combobox_typing = get_combobox(Dino.Application.get_default().settings.send_typing); combobox_typing.active_id = get_setting_id(conversation.send_typing); combobox_typing.changed.connect(() => { conversation.send_typing = get_setting(combobox_typing.active_id); } ); contact_details.add(details_headline, _("Send typing notifications"), "", combobox_typing); } if (conversation.type_ == Conversation.Type.CHAT) { ComboBoxText combobox_marker = get_combobox(Dino.Application.get_default().settings.send_marker); contact_details.add(DETAILS_HEADLINE_CHAT, _("Send read receipts"), "", combobox_marker); combobox_marker.active_id = get_setting_id(conversation.send_marker); combobox_marker.changed.connect(() => { conversation.send_marker = get_setting(combobox_marker.active_id); } ); ComboBoxText combobox_notifications = get_combobox(Dino.Application.get_default().settings.notifications); contact_details.add(DETAILS_HEADLINE_CHAT, _("Notifications"), "", combobox_notifications); combobox_notifications.active_id = get_notify_setting_id(conversation.notify_setting); combobox_notifications.changed.connect(() => { conversation.notify_setting = get_notify_setting(combobox_notifications.active_id); } ); } else if (conversation.type_ == Conversation.Type.GROUPCHAT) { ComboBoxText combobox = new ComboBoxText() { visible=true }; combobox.append("default", get_notify_setting_string(Conversation.NotifySetting.DEFAULT, conversation.get_notification_default_setting(stream_interactor))); combobox.append("highlight", get_notify_setting_string(Conversation.NotifySetting.HIGHLIGHT)); combobox.append("on", get_notify_setting_string(Conversation.NotifySetting.ON)); combobox.append("off", get_notify_setting_string(Conversation.NotifySetting.OFF)); contact_details.add(DETAILS_HEADLINE_ROOM, _("Notifications"), "", combobox); combobox.active_id = get_notify_setting_id(conversation.notify_setting); combobox.changed.connect(() => { conversation.notify_setting = get_notify_setting(combobox.active_id); } ); } } private Conversation.Setting get_setting(string id) { switch (id) { case "default": return Conversation.Setting.DEFAULT; case "on": return Conversation.Setting.ON; case "off": return Conversation.Setting.OFF; } assert_not_reached(); } private Conversation.NotifySetting get_notify_setting(string id) { switch (id) { case "default": return Conversation.NotifySetting.DEFAULT; case "on": return Conversation.NotifySetting.ON; case "off": return Conversation.NotifySetting.OFF; case "highlight": return Conversation.NotifySetting.HIGHLIGHT; } assert_not_reached(); } private string get_notify_setting_string(Conversation.NotifySetting setting, Conversation.NotifySetting? default_setting = null) { switch (setting) { case Conversation.NotifySetting.ON: return _("On"); case Conversation.NotifySetting.OFF: return _("Off"); case Conversation.NotifySetting.HIGHLIGHT: return _("Only when mentioned"); case Conversation.NotifySetting.DEFAULT: return _("Default: %s").printf(get_notify_setting_string(default_setting)); } assert_not_reached(); } private string get_setting_id(Conversation.Setting setting) { switch (setting) { case Conversation.Setting.DEFAULT: return "default"; case Conversation.Setting.ON: return "on"; case Conversation.Setting.OFF: return "off"; } assert_not_reached(); } private string get_notify_setting_id(Conversation.NotifySetting setting) { switch (setting) { case Conversation.NotifySetting.DEFAULT: return "default"; case Conversation.NotifySetting.ON: return "on"; case Conversation.NotifySetting.OFF: return "off"; case Conversation.NotifySetting.HIGHLIGHT: return "highlight"; } assert_not_reached(); } private ComboBoxText get_combobox(bool default_val) { ComboBoxText combobox = new ComboBoxText(); combobox = new ComboBoxText() { visible=true }; string default_setting = default_val ? _("On") : _("Off"); combobox.append("default", _("Default: %s").printf(default_setting) ); combobox.append("on", _("On")); combobox.append("off", _("Off")); return combobox; } } } dino-0.1.0/main/src/ui/conversation_list_titlebar.vala0000644000000000000000000000175613614354364021611 0ustar rootrootusing Gtk; using Dino.Entities; namespace Dino.Ui { [GtkTemplate (ui = "/im/dino/Dino/conversation_list_titlebar.ui")] public class ConversationListTitlebar : Gtk.Box { [GtkChild] private MenuButton add_button; [GtkChild] private MenuButton menu_button; private StreamInteractor stream_interactor; public ConversationListTitlebar(StreamInteractor stream_interactor, Window window) { this.stream_interactor = stream_interactor; create_add_menu(window); } private void create_add_menu(Window window) { Builder add_builder = new Builder.from_resource("/im/dino/Dino/menu_add.ui"); MenuModel add_menu_model = add_builder.get_object("menu_add") as MenuModel; add_button.set_menu_model(add_menu_model); Builder menu_builder = new Builder.from_resource("/im/dino/Dino/menu_app.ui"); MenuModel menu_menu_model = menu_builder.get_object("menu_app") as MenuModel; menu_button.set_menu_model(menu_menu_model); } } } dino-0.1.0/main/src/ui/conversation_list_titlebar_csd.vala0000644000000000000000000000222613614354364022433 0ustar rootrootusing Gtk; using Dino.Entities; namespace Dino.Ui { [GtkTemplate (ui = "/im/dino/Dino/conversation_list_titlebar_csd.ui")] public class ConversationListTitlebarCsd : Gtk.HeaderBar { [GtkChild] private MenuButton add_button; [GtkChild] private MenuButton menu_button; private StreamInteractor stream_interactor; public ConversationListTitlebarCsd(StreamInteractor stream_interactor, Window window) { this.stream_interactor = stream_interactor; custom_title = new Label("Dino") { visible = true, hexpand = true, xalign = 0 }; custom_title.get_style_context().add_class("title"); create_add_menu(window); } private void create_add_menu(Window window) { Builder add_builder = new Builder.from_resource("/im/dino/Dino/menu_add.ui"); MenuModel add_menu_model = add_builder.get_object("menu_add") as MenuModel; add_button.set_menu_model(add_menu_model); Builder menu_builder = new Builder.from_resource("/im/dino/Dino/menu_app.ui"); MenuModel menu_menu_model = menu_builder.get_object("menu_app") as MenuModel; menu_button.set_menu_model(menu_menu_model); } } } dino-0.1.0/main/src/ui/conversation_selector/0000755000000000000000000000000013614354364017712 5ustar rootrootdino-0.1.0/main/src/ui/conversation_selector/conversation_selector.vala0000644000000000000000000001166213614354364025177 0ustar rootrootusing Gee; using Gtk; using Xmpp; using Dino.Entities; namespace Dino.Ui { public class ConversationSelector : ListBox { public signal void conversation_selected(Conversation conversation); private StreamInteractor stream_interactor; private HashMap rows = new HashMap(Conversation.hash_func, Conversation.equals_func); public ConversationSelector init(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; stream_interactor.get_module(ConversationManager.IDENTITY).conversation_activated.connect(add_conversation); stream_interactor.get_module(ConversationManager.IDENTITY).conversation_deactivated.connect(remove_conversation); stream_interactor.get_module(ContentItemStore.IDENTITY).new_item.connect(on_content_item_received); Timeout.add_seconds(60, () => { foreach (ConversationSelectorRow row in rows.values) row.update(); return true; }); foreach (Conversation conversation in stream_interactor.get_module(ConversationManager.IDENTITY).get_active_conversations()) { add_conversation(conversation); } return this; } construct { get_style_context().add_class("sidebar"); set_header_func(header); set_sort_func(sort); realize.connect(() => { ListBoxRow? first_row = get_row_at_index(0); if (first_row != null) { select_row(first_row); row_activated(first_row); } }); } public override void row_activated(ListBoxRow r) { ConversationSelectorRow? row = r as ConversationSelectorRow; if (row != null) { conversation_selected(row.conversation); } } public void on_conversation_selected(Conversation conversation) { if (!rows.has_key(conversation)) { add_conversation(conversation); } rows[conversation].grab_focus(); this.select_row(rows[conversation]); } private void on_content_item_received(ContentItem item, Conversation conversation) { if (rows.has_key(conversation)) { invalidate_sort(); } } private void add_conversation(Conversation conversation) { ConversationSelectorRow row; if (!rows.has_key(conversation)) { row = new ConversationSelectorRow(stream_interactor, conversation); rows[conversation] = row; add(row); row.main_revealer.set_reveal_child(true); } invalidate_sort(); } private void select_fallback_conversation(Conversation conversation) { if (get_selected_row() == rows[conversation]) { int index = rows[conversation].get_index(); ListBoxRow? next_select_row = get_row_at_index(index + 1); if (next_select_row == null) { next_select_row = get_row_at_index(index - 1); } if (next_select_row != null) { select_row(next_select_row); row_activated(next_select_row); } } } private async void remove_conversation(Conversation conversation) { select_fallback_conversation(conversation); if (rows.has_key(conversation)) { yield rows[conversation].colapse(); remove(rows[conversation]); rows.unset(conversation); } } public void loop_conversations(bool backwards) { int index = get_selected_row().get_index(); int new_index = ((index + (backwards ? -1 : 1)) + rows.size) % rows.size; ListBoxRow? next_select_row = get_row_at_index(new_index); if (next_select_row != null) { select_row(next_select_row); row_activated(next_select_row); } } private void header(ListBoxRow row, ListBoxRow? before_row) { if (row.get_header() == null && before_row != null) { row.set_header(new Separator(Orientation.HORIZONTAL)); } else if (row.get_header() != null && before_row == null) { row.set_header(null); } } private int sort(ListBoxRow row1, ListBoxRow row2) { ConversationSelectorRow cr1 = row1 as ConversationSelectorRow; ConversationSelectorRow cr2 = row2 as ConversationSelectorRow; if (cr1 != null && cr2 != null) { Conversation c1 = cr1.conversation; Conversation c2 = cr2.conversation; if (c1.last_active == null) return -1; if (c2.last_active == null) return 1; int comp = c2.last_active.compare(c1.last_active); if (comp == 0) { return Util.get_conversation_display_name(stream_interactor, c1) .collate(Util.get_conversation_display_name(stream_interactor, c2)); } else { return comp; } } return 0; } } } dino-0.1.0/main/src/ui/conversation_selector/conversation_selector_row.vala0000644000000000000000000002706513614354364026072 0ustar rootrootusing Gee; using Gdk; using Gtk; using Pango; using Dino; using Dino.Entities; using Xmpp; namespace Dino.Ui { [GtkTemplate (ui = "/im/dino/Dino/conversation_selector/conversation_row.ui")] public class ConversationSelectorRow : ListBoxRow { [GtkChild] protected AvatarImage image; [GtkChild] protected Label name_label; [GtkChild] protected Label time_label; [GtkChild] protected Label nick_label; [GtkChild] protected Label message_label; [GtkChild] protected Button x_button; [GtkChild] protected Revealer time_revealer; [GtkChild] protected Revealer xbutton_revealer; [GtkChild] public Revealer main_revealer; public Conversation conversation { get; private set; } protected const int AVATAR_SIZE = 40; protected ContentItem? last_content_item; protected bool read = true; protected StreamInteractor stream_interactor; construct { name_label.attributes = new AttrList(); } public ConversationSelectorRow(StreamInteractor stream_interactor, Conversation conversation) { this.conversation = conversation; this.stream_interactor = stream_interactor; switch (conversation.type_) { case Conversation.Type.CHAT: stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.connect((account, jid, roster_item) => { if (conversation.account.equals(account) && conversation.counterpart.equals(jid)) { update_name_label(); } }); break; case Conversation.Type.GROUPCHAT: stream_interactor.get_module(MucManager.IDENTITY).room_name_set.connect((account, jid, room_name) => { if (conversation != null && conversation.counterpart.equals_bare(jid) && conversation.account.equals(account)) { update_name_label(); } }); stream_interactor.get_module(MucManager.IDENTITY).private_room_occupant_updated.connect((account, room, occupant) => { if (conversation != null && conversation.counterpart.equals_bare(room.bare_jid) && conversation.account.equals(account)) { update_name_label(); } }); break; case Conversation.Type.GROUPCHAT_PM: break; } // Set tooltip switch (conversation.type_) { case Conversation.Type.CHAT: has_tooltip = true; query_tooltip.connect ((x, y, keyboard_tooltip, tooltip) => { tooltip.set_custom(generate_tooltip()); return true; }); break; case Conversation.Type.GROUPCHAT: has_tooltip = true; set_tooltip_text(conversation.counterpart.bare_jid.to_string()); break; case Conversation.Type.GROUPCHAT_PM: break; } stream_interactor.get_module(ContentItemStore.IDENTITY).new_item.connect((item, c) => { if (conversation.equals(c)) { content_item_received(item); } }); last_content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_latest(conversation); x_button.clicked.connect(() => { stream_interactor.get_module(ConversationManager.IDENTITY).close_conversation(conversation); }); image.set_conversation(stream_interactor, conversation); conversation.notify["read-up-to"].connect(update_read); update_name_label(); content_item_received(); } public void update() { update_time_label(); } public void content_item_received(ContentItem? ci = null) { last_content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_latest(conversation) ?? ci; update_message_label(); update_time_label(); update_read(); } public async void colapse() { main_revealer.set_transition_type(RevealerTransitionType.SLIDE_UP); main_revealer.set_reveal_child(false); // Animations can be diabled (=> child_revealed immediately false). Wait for completion in case they're enabled. if (main_revealer.child_revealed) { main_revealer.notify["child-revealed"].connect(() => { Idle.add(colapse.callback); }); yield; } } protected void update_name_label() { name_label.label = Util.get_conversation_display_name(stream_interactor, conversation); } protected void update_time_label(DateTime? new_time = null) { if (last_content_item != null) { time_label.visible = true; time_label.label = get_relative_time(last_content_item.display_time.to_local()); } } protected void update_message_label() { if (last_content_item != null) { switch (last_content_item.type_) { case MessageItem.TYPE: MessageItem message_item = last_content_item as MessageItem; Message last_message = message_item.message; if (conversation.type_ == Conversation.Type.GROUPCHAT) { nick_label.label = Util.get_participant_display_name(stream_interactor, conversation, last_message.from, true) + ": "; } else { nick_label.label = last_message.direction == Message.DIRECTION_SENT ? _("Me") + ": " : ""; } message_label.attributes.filter((attr) => attr.equal(attr_style_new(Pango.Style.ITALIC))); message_label.label = Util.summarize_whitespaces_to_space(last_message.body); break; case FileItem.TYPE: FileItem file_item = last_content_item as FileItem; FileTransfer transfer = file_item.file_transfer; if (conversation.type_ == Conversation.Type.GROUPCHAT) { // TODO properly display nick for oneself nick_label.label = Util.get_participant_display_name(stream_interactor, conversation, file_item.file_transfer.from, true) + ": "; } else { nick_label.label = transfer.direction == Message.DIRECTION_SENT ? _("Me") + ": " : ""; } bool file_is_image = transfer.mime_type != null && transfer.mime_type.has_prefix("image"); message_label.attributes.insert(attr_style_new(Pango.Style.ITALIC)); if (transfer.direction == Message.DIRECTION_SENT) { message_label.label = (file_is_image ? _("Image sent") : _("File sent") ); } else { message_label.label = (file_is_image ? _("Image received") : _("File received") ); } break; } nick_label.visible = true; message_label.visible = true; } } protected void update_read() { bool current_read_status = !stream_interactor.get_module(ChatInteraction.IDENTITY).has_unread(conversation); if (read == current_read_status) return; read = current_read_status; if (read) { name_label.attributes.filter((attr) => attr.equal(attr_weight_new(Weight.BOLD))); time_label.attributes.filter((attr) => attr.equal(attr_weight_new(Weight.BOLD))); nick_label.attributes.filter((attr) => attr.equal(attr_weight_new(Weight.BOLD))); message_label.attributes.filter((attr) => attr.equal(attr_weight_new(Weight.BOLD))); } else { name_label.attributes.insert(attr_weight_new(Weight.BOLD)); time_label.attributes.insert(attr_weight_new(Weight.BOLD)); nick_label.attributes.insert(attr_weight_new(Weight.BOLD)); message_label.attributes.insert(attr_weight_new(Weight.BOLD)); } name_label.label = name_label.label; // TODO initializes redrawing, which would otherwise not happen. nicer? time_label.label = time_label.label; nick_label.label = nick_label.label; message_label.label = message_label.label; } protected Box get_fulljid_box(Jid full_jid) { Box box = new Box(Orientation.HORIZONTAL, 5) { visible=true }; Show show = stream_interactor.get_module(PresenceManager.IDENTITY).get_last_show(full_jid, conversation.account); Image image = new Image() { visible=true }; if (show.as == Show.AWAY) { image.set_from_icon_name("dino-status-away", IconSize.SMALL_TOOLBAR); } else if (show.as == Show.XA || show.as == Show.DND) { image.set_from_icon_name("dino-status-dnd", IconSize.SMALL_TOOLBAR); } else if (show.as == Show.CHAT) { image.set_from_icon_name("dino-status-chat", IconSize.SMALL_TOOLBAR); } else { image.set_from_icon_name("dino-status-online", IconSize.SMALL_TOOLBAR); } box.add(image); Label resource = new Label(full_jid.resourcepart) { visible=true }; resource.xalign = 0; box.add(resource); box.show_all(); return box; } public override void state_flags_changed(StateFlags flags) { StateFlags curr_flags = get_state_flags(); if ((curr_flags & StateFlags.PRELIGHT) != 0) { time_revealer.set_reveal_child(false); xbutton_revealer.set_reveal_child(true); } else { time_revealer.set_reveal_child(true); xbutton_revealer.set_reveal_child(false); } } private Widget generate_tooltip() { Builder builder = new Builder.from_resource("/im/dino/Dino/conversation_selector/chat_row_tooltip.ui"); Box main_box = builder.get_object("main_box") as Box; Box inner_box = builder.get_object("inner_box") as Box; Label jid_label = builder.get_object("jid_label") as Label; jid_label.label = conversation.counterpart.to_string(); Gee.List? full_jids = stream_interactor.get_module(PresenceManager.IDENTITY).get_full_jids(conversation.counterpart, conversation.account); if (full_jids != null) { for (int i = 0; i < full_jids.size; i++) { inner_box.add(get_fulljid_box(full_jids[i])); } } return main_box; } private static string get_relative_time(DateTime datetime) { DateTime now = new DateTime.now_local(); TimeSpan timespan = now.difference(datetime); if (timespan > 365 * TimeSpan.DAY) { return datetime.get_year().to_string(); } else if (timespan > 7 * TimeSpan.DAY) { // Day and month // xgettext:no-c-format return datetime.format(_("%b %d")); } else if (timespan > 2 * TimeSpan.DAY) { return datetime.format("%a"); } else if (datetime.get_day_of_month() != now.get_day_of_month()) { return _("Yesterday"); } else if (timespan > 9 * TimeSpan.MINUTE) { return datetime.format(Util.is_24h_format() ? /* xgettext:no-c-format */ /* Time in 24h format (w/o seconds) */ _("%Hโˆถ%M") : /* xgettext:no-c-format */ /* Time in 12h format (w/o seconds) */ _("%lโˆถ%M %p")); } else if (timespan > 1 * TimeSpan.MINUTE) { ulong mins = (ulong) (timespan.abs() / TimeSpan.MINUTE); return n("%i min ago", "%i mins ago", mins).printf(mins); } else { return _("Just now"); } } } } dino-0.1.0/main/src/ui/conversation_summary/0000755000000000000000000000000013614354364017567 5ustar rootrootdino-0.1.0/main/src/ui/conversation_summary/chat_state_populator.vala0000644000000000000000000001442513614354364024666 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; using Xmpp; namespace Dino.Ui.ConversationSummary { class ChatStatePopulator : Plugins.ConversationItemPopulator, Plugins.ConversationAdditionPopulator, Object { public string id { get { return "chat_state"; } } private StreamInteractor? stream_interactor; private Conversation? current_conversation; private Plugins.ConversationItemCollection? item_collection; private MetaChatStateItem? meta_item; public ChatStatePopulator(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; stream_interactor.get_module(CounterpartInteractionManager.IDENTITY).received_state.connect((account, jid, state) => { if (current_conversation != null && current_conversation.account.equals(account) && current_conversation.counterpart.equals_bare(jid)) { update_chat_state(account, jid); } }); stream_interactor.get_module(MessageProcessor.IDENTITY).message_sent.connect((message, conversation) => { if (conversation.equals(current_conversation)) { update_chat_state(conversation.account, conversation.counterpart); } }); } public void init(Conversation conversation, Plugins.ConversationItemCollection item_collection, Plugins.WidgetType type) { current_conversation = conversation; this.item_collection = item_collection; this.meta_item = null; update_chat_state(conversation.account, conversation.counterpart); } public void close(Conversation conversation) { } public void populate_timespan(Conversation conversation, DateTime from, DateTime to) { } private void update_chat_state(Account account, Jid jid) { HashMap? states = stream_interactor.get_module(CounterpartInteractionManager.IDENTITY).get_chat_states(current_conversation); StateType? state_type = null; Gee.List jids = new ArrayList(); if (states != null) { Gee.List composing = new ArrayList(); Gee.List paused = new ArrayList(); foreach (Jid j in states.keys) { string state = states[j]; if (state == Xep.ChatStateNotifications.STATE_COMPOSING) { composing.add(j); } else if (state == Xep.ChatStateNotifications.STATE_PAUSED) { paused.add(j); } } if (composing.size == 1 || (composing.size > 1 && current_conversation.type_ != Conversation.Type.GROUPCHAT)) { state_type = StateType.TYPING; jids.add(composing[0]); } else if (paused.size >= 1 && current_conversation.type_ != Conversation.Type.GROUPCHAT) { state_type = StateType.PAUSED; jids.add(paused[0]); } else if (composing.size > 1) { state_type = StateType.TYPING; jids = composing; } } if (meta_item != null && state_type == null) { item_collection.remove_item(meta_item); meta_item = null; } else if (meta_item != null && state_type != null) { meta_item.set_new(state_type, jids); } else if (state_type != null) { meta_item = new MetaChatStateItem(stream_interactor, current_conversation, jid, state_type, jids); item_collection.insert_item(meta_item); } } } private enum StateType { TYPING, PAUSED } private class MetaChatStateItem : Plugins.MetaConversationItem { public override Jid? jid { get; set; } public override bool dim { get; set; default=true; } public override DateTime sort_time { get; set; default=new DateTime.now_utc().add_years(10); } public override bool can_merge { get; set; default=false; } public override bool requires_avatar { get; set; default=false; } public override bool requires_header { get; set; default=false; } private StreamInteractor stream_interactor; private Conversation conversation; private StateType state_type; private Gee.List jids = new ArrayList(); private Label label; private AvatarImage image; public MetaChatStateItem(StreamInteractor stream_interactor, Conversation conversation, Jid jid, StateType state_type, Gee.List jids) { this.stream_interactor = stream_interactor; this.conversation = conversation; this.jid = jid; this.state_type = state_type; this.jids = jids; } public override Object? get_widget(Plugins.WidgetType widget_type) { label = new Label("") { xalign=0, vexpand=true, visible=true }; label.get_style_context().add_class("dim-label"); image = new AvatarImage() { margin_top=2, valign=Align.START, visible=true }; Box image_content_box = new Box(Orientation.HORIZONTAL, 8) { visible=true }; image_content_box.add(image); image_content_box.add(label); update(); return image_content_box; } public void set_new(StateType state_type, Gee.List jids) { this.state_type = state_type; this.jids = jids; update(); } private void update() { if (image == null || label == null) return; image.set_conversation_participants(stream_interactor, conversation, jids.to_array()); Gee.List display_names = new ArrayList(); foreach (Jid jid in jids) { display_names.add(Util.get_participant_display_name(stream_interactor, conversation, jid)); } string new_text = ""; if (jids.size > 3) { new_text = _("%s, %s and %i others").printf(display_names[0], display_names[1], jids.size - 2); } else if (jids.size == 3) { new_text = _("%s, %s and %s").printf(display_names[0], display_names[1], display_names[2]); } else if (jids.size == 2) { new_text =_("%s and %s").printf(display_names[0], display_names[1]); } else { new_text = display_names[0]; } if (state_type == StateType.TYPING) { new_text += " " + n("is typingโ€ฆ", "are typingโ€ฆ", jids.size); } else { new_text += " " + _("has stopped typing"); } label.label = new_text; } } } dino-0.1.0/main/src/ui/conversation_summary/content_item_widget_factory.vala0000644000000000000000000001056113614354364026221 0ustar rootrootusing Gee; using Gdk; using Gtk; using Pango; using Xmpp; using Dino.Entities; namespace Dino.Ui.ConversationSummary { public class ContentItemWidgetFactory : Object { private StreamInteractor stream_interactor; private HashMap generators = new HashMap(); public ContentItemWidgetFactory(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; generators[MessageItem.TYPE] = new MessageItemWidgetGenerator(stream_interactor); generators[FileItem.TYPE] = new FileItemWidgetGenerator(stream_interactor); } public Widget? get_widget(ContentItem item) { WidgetGenerator? generator = generators[item.type_]; if (generator != null) { return (Widget?) generator.get_widget(item); } return null; } public void register_widget_generator(WidgetGenerator generator) { generators[generator.handles_type] = generator; } } public interface WidgetGenerator : Object { public abstract string handles_type { get; set; } public abstract Object get_widget(ContentItem item); } public class MessageItemWidgetGenerator : WidgetGenerator, Object { public string handles_type { get; set; default=FileItem.TYPE; } private StreamInteractor stream_interactor; public MessageItemWidgetGenerator(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public Object get_widget(ContentItem item) { MessageItem message_item = item as MessageItem; Conversation conversation = message_item.conversation; Message message = message_item.message; Label label = new Label("") { use_markup=true, xalign=0, selectable=true, wrap=true, wrap_mode=Pango.WrapMode.WORD_CHAR, vexpand=true, visible=true }; string markup_text = message.body; if (markup_text.length > 10000) { markup_text = markup_text.substring(0, 10000) + " [" + _("Message too long") + "]"; } if (message_item.message.body.has_prefix("/me")) { markup_text = markup_text.substring(3); } if (conversation.type_ == Conversation.Type.GROUPCHAT) { markup_text = Util.parse_add_markup(markup_text, conversation.nickname, true, true); } else { markup_text = Util.parse_add_markup(markup_text, null, true, true); } if (message_item.message.body.has_prefix("/me")) { string display_name = Util.get_participant_display_name(stream_interactor, conversation, message.from); update_me_style(stream_interactor, message.real_jid ?? message.from, display_name, conversation.account, label, markup_text); label.realize.connect(() => update_me_style(stream_interactor, message.real_jid ?? message.from, display_name, conversation.account, label, markup_text)); label.style_updated.connect(() => update_me_style(stream_interactor, message.real_jid ?? message.from, display_name, conversation.account, label, markup_text)); } int only_emoji_count = Util.get_only_emoji_count(markup_text); if (only_emoji_count != -1) { string size_str = only_emoji_count < 5 ? "xx-large" : "large"; markup_text = @"" + markup_text + ""; } label.label = markup_text; return label; } public static void update_me_style(StreamInteractor stream_interactor, Jid jid, string display_name, Account account, Label label, string action_text) { string color = Util.get_name_hex_color(stream_interactor, account, jid, Util.is_dark_theme(label)); label.label = @"$(Markup.escape_text(display_name))" + action_text; } } public class FileItemWidgetGenerator : WidgetGenerator, Object { public StreamInteractor stream_interactor; public string handles_type { get; set; default=FileItem.TYPE; } private const int MAX_HEIGHT = 300; private const int MAX_WIDTH = 600; public FileItemWidgetGenerator(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public Object get_widget(ContentItem item) { FileItem file_item = item as FileItem; FileTransfer transfer = file_item.file_transfer; return new FileWidget(stream_interactor, transfer) { visible=true }; } } } dino-0.1.0/main/src/ui/conversation_summary/content_populator.vala0000644000000000000000000001043713614354364024220 0ustar rootrootusing Gee; using Gtk; using Xmpp; using Dino.Entities; namespace Dino.Ui.ConversationSummary { public class ContentProvider : ContentItemCollection, Object { private StreamInteractor stream_interactor; private ContentItemWidgetFactory widget_factory; private Conversation? current_conversation; private Plugins.ConversationItemCollection? item_collection; public ContentProvider(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; this.widget_factory = new ContentItemWidgetFactory(stream_interactor); } public void init(Plugins.ConversationItemCollection item_collection, Conversation conversation, Plugins.WidgetType type) { if (current_conversation != null) { stream_interactor.get_module(ContentItemStore.IDENTITY).uninit(current_conversation, this); } current_conversation = conversation; this.item_collection = item_collection; stream_interactor.get_module(ContentItemStore.IDENTITY).init(conversation, this); } public void insert_item(ContentItem item) { item_collection.insert_item(new ContentMetaItem(item, widget_factory)); } public void remove_item(ContentItem item) { } public Gee.List populate_latest(Conversation conversation, int n) { Gee.List items = stream_interactor.get_module(ContentItemStore.IDENTITY).get_n_latest(conversation, n); Gee.List ret = new ArrayList(); foreach (ContentItem item in items) { ret.add(new ContentMetaItem(item, widget_factory)); } return ret; } public Gee.List populate_before(Conversation conversation, ContentItem before_item, int n) { Gee.List ret = new ArrayList(); Gee.List items = stream_interactor.get_module(ContentItemStore.IDENTITY).get_before(conversation, before_item, n); foreach (ContentItem item in items) { ret.add(new ContentMetaItem(item, widget_factory)); } return ret; } public Gee.List populate_after(Conversation conversation, ContentItem after_item, int n) { Gee.List ret = new ArrayList(); Gee.List items = stream_interactor.get_module(ContentItemStore.IDENTITY).get_after(conversation, after_item, n); foreach (ContentItem item in items) { ret.add(new ContentMetaItem(item, widget_factory)); } return ret; } public ContentMetaItem get_content_meta_item(ContentItem content_item) { return new ContentMetaItem(content_item, widget_factory); } } public class ContentMetaItem : Plugins.MetaConversationItem { public override Jid? jid { get; set; } public override DateTime sort_time { get; set; } public override DateTime? display_time { get; set; } public override Encryption encryption { get; set; } public ContentItem content_item; private ContentItemWidgetFactory widget_factory; public ContentMetaItem(ContentItem content_item, ContentItemWidgetFactory widget_factory) { this.jid = content_item.jid; this.sort_time = content_item.sort_time; this.seccondary_sort_indicator = (long) content_item.display_time.to_unix(); this.tertiary_sort_indicator = content_item.id; this.display_time = content_item.display_time; this.encryption = content_item.encryption; this.mark = content_item.mark; WeakRef weak_item = WeakRef(content_item); content_item.notify["mark"].connect(() => { ContentItem? ci = weak_item.get() as ContentItem; if (ci == null) return; this.mark = ci.mark; }); this.can_merge = true; this.requires_avatar = true; this.requires_header = true; this.content_item = content_item; this.widget_factory = widget_factory; } public override bool can_merge { get; set; default=true; } public override bool requires_avatar { get; set; default=true; } public override bool requires_header { get; set; default=true; } public override Object? get_widget(Plugins.WidgetType type) { return widget_factory.get_widget(content_item); } } } dino-0.1.0/main/src/ui/conversation_summary/conversation_item_skeleton.vala0000644000000000000000000002146313614354364026076 0ustar rootrootusing Gee; using Gdk; using Gtk; using Markup; using Dino.Entities; namespace Dino.Ui.ConversationSummary { public class ConversationItemSkeleton : EventBox { private AvatarImage image = new AvatarImage() { margin_top=2, valign=Align.START, visible=true, allow_gray = false }; public bool show_skeleton { get; set; } public bool last_group_item { get; set; } public StreamInteractor stream_interactor; public Conversation conversation { get; set; } public Plugins.MetaConversationItem item; private Box image_content_box = new Box(Orientation.HORIZONTAL, 8) { visible=true }; private Box header_content_box = new Box(Orientation.VERTICAL, 0) { visible=true }; private ItemMetaDataHeader metadata_header; public ConversationItemSkeleton(StreamInteractor stream_interactor, Conversation conversation, Plugins.MetaConversationItem item) { this.stream_interactor = stream_interactor; this.conversation = conversation; this.item = item; this.get_style_context().add_class("message-box"); if (item.requires_avatar) { image.set_conversation_participant(stream_interactor, conversation, item.jid); image_content_box.add(image); } if (item.requires_header) { metadata_header = new ItemMetaDataHeader(stream_interactor, conversation, item) { visible=true }; header_content_box.add(metadata_header); } Widget? widget = item.get_widget(Plugins.WidgetType.GTK) as Widget; if (widget != null) { widget.valign = Align.END; header_content_box.add(widget); } image_content_box.add(header_content_box); this.add(image_content_box); if (item.get_type().is_a(typeof(ContentMetaItem))) { this.motion_notify_event.connect((event) => { this.set_state_flags(StateFlags.PRELIGHT, false); return false; }); this.enter_notify_event.connect((event) => { this.set_state_flags(StateFlags.PRELIGHT, false); return false; }); this.leave_notify_event.connect((event) => { this.unset_state_flags(StateFlags.PRELIGHT); return false; }); } this.notify["show-skeleton"].connect(update_margin); this.notify["last-group-item"].connect(update_margin); this.show_skeleton = true; this.last_group_item = true; update_margin(); this.notify["show-skeleton"].connect(update_margin); } public void update_time() { if (metadata_header != null) { metadata_header.update_time(); } } public void update_margin() { image.visible = this.show_skeleton; if (metadata_header != null) { metadata_header.visible = this.show_skeleton; } image_content_box.margin_start = this.show_skeleton ? 15 : 58; image_content_box.margin_end = 15; if (this.show_skeleton && this.last_group_item) { image_content_box.margin_top = 8; image_content_box.margin_bottom = 8; } else { image_content_box.margin_top = 4; image_content_box.margin_bottom = 4; } } } [GtkTemplate (ui = "/im/dino/Dino/conversation_summary/item_metadata_header.ui")] public class ItemMetaDataHeader : Box { [GtkChild] public Label name_label; [GtkChild] public Label dot_label; [GtkChild] public Label time_label; [GtkChild] public Image encryption_image; [GtkChild] public Image received_image; public static IconSize ICON_SIZE_HEADER = Gtk.icon_size_register("im.dino.Dino.HEADER_ICON", 17, 12); private StreamInteractor stream_interactor; private Conversation conversation; private Plugins.MetaConversationItem item; private ArrayList items = new ArrayList(); public ItemMetaDataHeader(StreamInteractor stream_interactor, Conversation conversation, Plugins.MetaConversationItem item) { this.stream_interactor = stream_interactor; this.conversation = conversation; this.item = item; items.add(item); update_name_label(); name_label.style_updated.connect(update_name_label); if (item.encryption != Encryption.NONE) { encryption_image.visible = true; encryption_image.set_from_icon_name("dino-changes-prevent-symbolic", ICON_SIZE_HEADER); } update_time(); item.notify["mark"].connect_after(update_received_mark); update_received_mark(); } public void update_time() { if (item.display_time != null) { time_label.label = get_relative_time(item.display_time.to_local()).to_string(); } } private void update_name_label() { string display_name = Markup.escape_text(Util.get_participant_display_name(stream_interactor, conversation, item.jid)); string color = Util.get_name_hex_color(stream_interactor, conversation.account, item.jid, Util.is_dark_theme(name_label)); name_label.label = @"$display_name"; } private void update_received_mark() { bool all_received = true; bool all_read = true; bool all_sent = true; foreach (Plugins.MetaConversationItem item in items) { if (item.mark == Message.Marked.WONTSEND) { received_image.visible = true; received_image.set_from_icon_name("dialog-warning-symbolic", ICON_SIZE_HEADER); Util.force_error_color(received_image); Util.force_error_color(encryption_image); Util.force_error_color(time_label); return; } else if (item.mark != Message.Marked.READ) { all_read = false; if (item.mark != Message.Marked.RECEIVED) { all_received = false; if (item.mark == Message.Marked.UNSENT) { all_sent = false; } } } } if (all_read) { received_image.visible = true; received_image.set_from_icon_name("dino-double-tick-symbolic", ICON_SIZE_HEADER); } else if (all_received) { received_image.visible = true; received_image.set_from_icon_name("dino-tick-symbolic", ICON_SIZE_HEADER); } else if (!all_sent) { received_image.visible = true; received_image.set_from_icon_name("image-loading-symbolic", ICON_SIZE_HEADER); } else if (received_image.visible) { received_image.set_from_icon_name("image-loading-symbolic", ICON_SIZE_HEADER); } } public static string format_time(DateTime datetime, string format_24h, string format_12h) { string format = Util.is_24h_format() ? format_24h : format_12h; if (!get_charset(null)) { // No UTF-8 support, use simple colon for time instead format = format.replace("โˆถ", ":"); } return datetime.format(format); } public static string get_relative_time(DateTime datetime) { DateTime now = new DateTime.now_local(); TimeSpan timespan = now.difference(datetime); if (timespan > 365 * TimeSpan.DAY) { return format_time(datetime, /* xgettext:no-c-format */ /* Date + time in 24h format (w/o seconds) */ _("%x, %Hโˆถ%M"), /* xgettext:no-c-format */ /* Date + time in 12h format (w/o seconds)*/ _("%x, %lโˆถ%M %p")); } else if (timespan > 7 * TimeSpan.DAY) { return format_time(datetime, /* xgettext:no-c-format */ /* Month, day and time in 24h format (w/o seconds) */ _("%b %d, %Hโˆถ%M"), /* xgettext:no-c-format */ /* Month, day and time in 12h format (w/o seconds) */ _("%b %d, %lโˆถ%M %p")); } else if (datetime.get_day_of_month() != now.get_day_of_month()) { return format_time(datetime, /* xgettext:no-c-format */ /* Day of week and time in 24h format (w/o seconds) */ _("%a, %Hโˆถ%M"), /* xgettext:no-c-format */ /* Day of week and time in 12h format (w/o seconds) */_("%a, %lโˆถ%M %p")); } else if (timespan > 9 * TimeSpan.MINUTE) { return format_time(datetime, /* xgettext:no-c-format */ /* Time in 24h format (w/o seconds) */ _("%Hโˆถ%M"), /* xgettext:no-c-format */ /* Time in 12h format (w/o seconds) */ _("%lโˆถ%M %p")); } else if (timespan > TimeSpan.MINUTE) { ulong mins = (ulong) (timespan.abs() / TimeSpan.MINUTE); /* xgettext:this is the beginning of a sentence. */ return n("%i min ago", "%i mins ago", mins).printf(mins); } else { return _("Just now"); } } } } dino-0.1.0/main/src/ui/conversation_summary/conversation_view.vala0000644000000000000000000003574513614354364024216 0ustar rootrootusing Gee; using Gtk; using Pango; using Dino.Entities; namespace Dino.Ui.ConversationSummary { [GtkTemplate (ui = "/im/dino/Dino/conversation_summary/view.ui")] public class ConversationView : Box, Plugins.ConversationItemCollection, Plugins.NotificationCollection { public Conversation? conversation { get; private set; } [GtkChild] public ScrolledWindow scrolled; [GtkChild] private Revealer notification_revealer; [GtkChild] private Box notifications; [GtkChild] private Box main; [GtkChild] private Stack stack; private StreamInteractor stream_interactor; private Gee.TreeSet content_items = new Gee.TreeSet(compare_meta_items); private Gee.TreeSet meta_items = new TreeSet(compare_meta_items); private Gee.HashMap item_item_skeletons = new Gee.HashMap(); private Gee.HashMap widgets = new Gee.HashMap(); private Gee.List item_skeletons = new Gee.ArrayList(); private ContentProvider content_populator; private SubscriptionNotitication subscription_notification; private double? was_value; private double? was_upper; private double? was_page_size; private Mutex reloading_mutex = Mutex(); private bool animate = false; private bool firstLoad = true; private bool at_current_content = true; private bool reload_messages = true; public ConversationView init(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; scrolled.vadjustment.notify["upper"].connect_after(on_upper_notify); scrolled.vadjustment.notify["value"].connect(on_value_notify); content_populator = new ContentProvider(stream_interactor); subscription_notification = new SubscriptionNotitication(stream_interactor); add_meta_notification.connect(on_add_meta_notification); remove_meta_notification.connect(on_remove_meta_notification); Application app = GLib.Application.get_default() as Application; app.plugin_registry.register_conversation_addition_populator(new ChatStatePopulator(stream_interactor)); app.plugin_registry.register_conversation_addition_populator(new DateSeparatorPopulator(stream_interactor)); Timeout.add_seconds(60, () => { foreach (ConversationItemSkeleton item_skeleton in item_skeletons) { item_skeleton.update_time(); } return true; }); return this; } public void initialize_for_conversation(Conversation? conversation) { // Workaround for rendering issues if (firstLoad) { main.visible = false; Idle.add(() => { main.visible=true; return false; }); firstLoad = false; } stack.set_visible_child_name("void"); clear(); initialize_for_conversation_(conversation); display_latest(); stack.set_visible_child_name("main"); } public void initialize_around_message(Conversation conversation, ContentItem content_item) { stack.set_visible_child_name("void"); clear(); initialize_for_conversation_(conversation); Gee.List before_items = content_populator.populate_before(conversation, content_item, 40); foreach (ContentMetaItem item in before_items) { do_insert_item(item); } ContentMetaItem meta_item = content_populator.get_content_meta_item(content_item); meta_item.can_merge = false; Widget w = insert_new(meta_item); content_items.add(meta_item); meta_items.add(meta_item); Gee.List after_items = content_populator.populate_after(conversation, content_item, 40); foreach (ContentMetaItem item in after_items) { do_insert_item(item); } if (after_items.size == 40) { at_current_content = false; } // Compute where to jump to for centered message, jump, highlight. reload_messages = false; Timeout.add(700, () => { int h = 0, i = 0; bool @break = false; main.@foreach((widget) => { if (widget == w || @break) { @break = true; return; } h += widget.get_allocated_height(); i++; }); scrolled.vadjustment.value = h - scrolled.vadjustment.page_size * 1/3; w.get_style_context().add_class("highlight-once"); reload_messages = true; stack.set_visible_child_name("main"); return false; }); } private void initialize_for_conversation_(Conversation? conversation) { // Deinitialize old conversation Dino.Application app = Dino.Application.get_default(); if (this.conversation != null) { foreach (Plugins.ConversationItemPopulator populator in app.plugin_registry.conversation_addition_populators) { populator.close(conversation); } foreach (Plugins.NotificationPopulator populator in app.plugin_registry.notification_populators) { populator.close(conversation); } } // Clear data structures clear_notifications(); this.conversation = conversation; // Init for new conversation foreach (Plugins.ConversationItemPopulator populator in app.plugin_registry.conversation_addition_populators) { populator.init(conversation, this, Plugins.WidgetType.GTK); } content_populator.init(this, conversation, Plugins.WidgetType.GTK); subscription_notification.init(conversation, this); animate = false; Timeout.add(20, () => { animate = true; return false; }); } private void display_latest() { Gee.List items = content_populator.populate_latest(conversation, 40); foreach (ContentMetaItem item in items) { do_insert_item(item); } Application app = GLib.Application.get_default() as Application; foreach (Plugins.NotificationPopulator populator in app.plugin_registry.notification_populators) { populator.init(conversation, this, Plugins.WidgetType.GTK); } Idle.add(() => { on_value_notify(); return false; }); } public void insert_item(Plugins.MetaConversationItem item) { if (meta_items.size > 0) { bool after_last = meta_items.last().sort_time.compare(item.sort_time) <= 0; bool within_range = meta_items.last().sort_time.compare(item.sort_time) > 0 && meta_items.first().sort_time.compare(item.sort_time) < 0; bool accept = within_range || (at_current_content && after_last); if (!accept) { return; } } do_insert_item(item); } public void do_insert_item(Plugins.MetaConversationItem item) { lock (meta_items) { insert_new(item); if (item as ContentMetaItem != null) { content_items.add(item); } meta_items.add(item); } inserted_item(item); } private void remove_item(Plugins.MetaConversationItem item) { ConversationItemSkeleton? skeleton = item_item_skeletons[item]; if (skeleton != null) { widgets[item].destroy(); widgets.unset(item); skeleton.destroy(); item_skeletons.remove(skeleton); item_item_skeletons.unset(item); content_items.remove(item); meta_items.remove(item); } removed_item(item); } public void on_add_meta_notification(Plugins.MetaConversationNotification notification) { Widget? widget = (Widget) notification.get_widget(Plugins.WidgetType.GTK); if (widget != null) { add_notification(widget); } } public void on_remove_meta_notification(Plugins.MetaConversationNotification notification){ Widget? widget = (Widget) notification.get_widget(Plugins.WidgetType.GTK); if (widget != null) { remove_notification(widget); } } public void add_notification(Widget widget) { notifications.add(widget); Timeout.add(20, () => { notification_revealer.transition_duration = 200; notification_revealer.reveal_child = true; return false; }); } public void remove_notification(Widget widget) { notification_revealer.reveal_child = false; widget.destroy(); } private Widget insert_new(Plugins.MetaConversationItem item) { Plugins.MetaConversationItem? lower_item = meta_items.lower(item); // Fill datastructure ConversationItemSkeleton item_skeleton = new ConversationItemSkeleton(stream_interactor, conversation, item) { visible=true }; item_item_skeletons[item] = item_skeleton; int index = lower_item != null ? item_skeletons.index_of(item_item_skeletons[lower_item]) + 1 : 0; item_skeletons.insert(index, item_skeleton); // Insert widget Widget insert = item_skeleton; if (animate) { Revealer revealer = new Revealer() {transition_duration = 200, transition_type = RevealerTransitionType.SLIDE_UP, visible = true}; revealer.add(item_skeleton); insert = revealer; main.add(insert); revealer.reveal_child = true; } else { main.add(insert); } widgets[item] = insert; main.reorder_child(insert, index); if (lower_item != null) { if (can_merge(item, lower_item)) { ConversationItemSkeleton lower_skeleton = item_item_skeletons[lower_item]; item_skeleton.show_skeleton = false; lower_skeleton.last_group_item = false; } } Plugins.MetaConversationItem? upper_item = meta_items.higher(item); if (upper_item != null) { if (!can_merge(upper_item, item)) { ConversationItemSkeleton upper_skeleton = item_item_skeletons[upper_item]; upper_skeleton.show_skeleton = true; } } // If an item from the past was added, add everything between that item and the (post-)first present item if (index == 0) { Dino.Application app = Dino.Application.get_default(); if (item_skeletons.size == 1) { foreach (Plugins.ConversationAdditionPopulator populator in app.plugin_registry.conversation_addition_populators) { populator.populate_timespan(conversation, item.sort_time, new DateTime.now_utc()); } } else { foreach (Plugins.ConversationAdditionPopulator populator in app.plugin_registry.conversation_addition_populators) { populator.populate_timespan(conversation, item.sort_time, meta_items.higher(item).sort_time); } } } return insert; } private bool can_merge(Plugins.MetaConversationItem upper_item /*more recent, displayed below*/, Plugins.MetaConversationItem lower_item /*less recent, displayed above*/) { return upper_item.display_time != null && lower_item.display_time != null && upper_item.display_time.difference(lower_item.display_time) < TimeSpan.MINUTE && upper_item.jid.equals(lower_item.jid) && upper_item.encryption == lower_item.encryption && (upper_item.mark == Message.Marked.WONTSEND) == (lower_item.mark == Message.Marked.WONTSEND); } private void on_upper_notify() { if (was_upper == null || scrolled.vadjustment.value > was_upper - was_page_size - 1) { // scrolled down or content smaller than page size if (at_current_content) { scrolled.vadjustment.value = scrolled.vadjustment.upper - scrolled.vadjustment.page_size; // scroll down } } else if (scrolled.vadjustment.value < scrolled.vadjustment.upper - scrolled.vadjustment.page_size - 1) { scrolled.vadjustment.value = scrolled.vadjustment.upper - was_upper + scrolled.vadjustment.value; // stay at same content } was_upper = scrolled.vadjustment.upper; was_page_size = scrolled.vadjustment.page_size; was_value = scrolled.vadjustment.value; reloading_mutex.trylock(); reloading_mutex.unlock(); } private void on_value_notify() { if (scrolled.vadjustment.value < 400) { load_earlier_messages(); } else if (scrolled.vadjustment.upper - (scrolled.vadjustment.value + scrolled.vadjustment.page_size) < 400) { load_later_messages(); } } private void load_earlier_messages() { was_value = scrolled.vadjustment.value; if (!reloading_mutex.trylock()) return; if (meta_items.size > 0) { Gee.List items = content_populator.populate_before(conversation, (content_items.first() as ContentMetaItem).content_item, 20); foreach (ContentMetaItem item in items) { do_insert_item(item); } } else { reloading_mutex.unlock(); } } private void load_later_messages() { if (!reloading_mutex.trylock()) return; if (meta_items.size > 0 && !at_current_content) { Gee.List items = content_populator.populate_after(conversation, (content_items.last() as ContentMetaItem).content_item, 20); if (items.size == 0) { at_current_content = true; } foreach (ContentMetaItem item in items) { do_insert_item(item); } } else { reloading_mutex.unlock(); } } private static int compare_meta_items(Plugins.MetaConversationItem a, Plugins.MetaConversationItem b) { int cmp1 = a.sort_time.compare(b.sort_time); if (cmp1 == 0) { double cmp2 = a.seccondary_sort_indicator - b.seccondary_sort_indicator; if (cmp2 == 0) { return (int) (a.tertiary_sort_indicator - b.tertiary_sort_indicator); } return (int) cmp2; } return cmp1; } private void clear() { was_upper = null; was_page_size = null; content_items.clear(); meta_items.clear(); item_skeletons.clear(); item_item_skeletons.clear(); widgets.clear(); main.@foreach((widget) => { widget.destroy(); }); } private void clear_notifications() { notifications.@foreach((widget) => { widget.destroy(); }); notification_revealer.transition_duration = 0; notification_revealer.set_reveal_child(false); } } } dino-0.1.0/main/src/ui/conversation_summary/date_separator_populator.vala0000644000000000000000000001011413614354364025533 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; using Xmpp; namespace Dino.Ui.ConversationSummary { class DateSeparatorPopulator : Plugins.ConversationItemPopulator, Plugins.ConversationAdditionPopulator, Object { public string id { get { return "date_separator"; } } private StreamInteractor stream_interactor; private Conversation? current_conversation; private Plugins.ConversationItemCollection? item_collection; private Gee.TreeSet insert_times; public DateSeparatorPopulator(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public void init(Conversation conversation, Plugins.ConversationItemCollection item_collection, Plugins.WidgetType type) { current_conversation = conversation; this.item_collection = item_collection; item_collection.inserted_item.connect(on_inserted_item); this.insert_times = new TreeSet((a, b) => { return a.compare(b); }); } public void close(Conversation conversation) { item_collection.inserted_item.disconnect(on_inserted_item); } public void populate_timespan(Conversation conversation, DateTime after, DateTime before) { } private void on_inserted_item(Plugins.MetaConversationItem item) { if (!(item is ContentMetaItem)) return; DateTime time = item.sort_time.to_local(); DateTime msg_date = new DateTime.local(time.get_year(), time.get_month(), time.get_day_of_month(), 0, 0, 0); if (!insert_times.contains(msg_date)) { if (insert_times.lower(msg_date) != null) { item_collection.insert_item(new MetaDateItem(msg_date.to_utc())); } else if (insert_times.size > 0) { item_collection.insert_item(new MetaDateItem(insert_times.first().to_utc())); } insert_times.add(msg_date); } } } public class MetaDateItem : Plugins.MetaConversationItem { public override DateTime sort_time { get; set; } public override bool can_merge { get; set; default=false; } public override bool requires_avatar { get; set; default=false; } public override bool requires_header { get; set; default=false; } private DateTime date; public MetaDateItem(DateTime date) { this.date = date; this.sort_time = date; } public override Object? get_widget(Plugins.WidgetType widget_type) { Box box = new Box(Orientation.HORIZONTAL, 10) { width_request=300, halign=Align.CENTER, visible=true }; box.add(new Separator(Orientation.HORIZONTAL) { valign=Align.CENTER, hexpand=true, visible=true }); string date_str = get_relative_time(date); Label label = new Label(@"$date_str") { use_markup=true, halign=Align.CENTER, hexpand=false, visible=true }; label.get_style_context().add_class("dim-label"); box.add(label); box.add(new Separator(Orientation.HORIZONTAL) { valign=Align.CENTER, hexpand=true, visible=true }); return box; } private static string get_relative_time(DateTime time) { DateTime time_local = time.to_local(); DateTime now_local = new DateTime.now_local(); if (time_local.get_year() == now_local.get_year() && time_local.get_month() == now_local.get_month() && time_local.get_day_of_month() == now_local.get_day_of_month()) { return _("Today"); } DateTime now_local_minus = now_local.add_days(-1); if (time_local.get_year() == now_local_minus.get_year() && time_local.get_month() == now_local_minus.get_month() && time_local.get_day_of_month() == now_local_minus.get_day_of_month()) { return _("Yesterday"); } if (time_local.get_year() != now_local.get_year()) { return time_local.format("%x"); } TimeSpan timespan = now_local.difference(time_local); if (timespan < 7 * TimeSpan.DAY) { return time_local.format(_("%a, %b %d")); } else { return time_local.format(_("%b %d")); } } } } dino-0.1.0/main/src/ui/conversation_summary/file_widget.vala0000644000000000000000000003557413614354364022734 0ustar rootrootusing Gee; using Gdk; using Gtk; using Pango; using Dino.Entities; namespace Dino.Ui.ConversationSummary { public class FileWidget : Box { enum State { IMAGE, DEFAULT } private const int MAX_HEIGHT = 300; private const int MAX_WIDTH = 600; private StreamInteractor stream_interactor; private FileTransfer file_transfer; private State state; // default box private Box main_box; private Image content_type_image; private Image download_image; private Spinner spinner; private Label mime_label; private Stack image_stack; private Widget content; private bool pointer_inside = false; public FileWidget(StreamInteractor stream_interactor, FileTransfer file_transfer) { this.stream_interactor = stream_interactor; this.file_transfer = file_transfer; load_widget.begin(); } private async void load_widget() { if (show_image()) { content = yield get_image_widget(file_transfer); if (content != null) { this.state = State.IMAGE; this.add(content); return; } } content = get_default_widget(file_transfer); this.state = State.DEFAULT; this.add(content); } private async Widget? get_image_widget(FileTransfer file_transfer) { // Load and prepare image in tread Thread thread = new Thread (null, () => { Image image = new Image() { halign=Align.START, visible = true }; Gdk.Pixbuf pixbuf; try { pixbuf = new Gdk.Pixbuf.from_file(file_transfer.get_file().get_path()); } catch (Error error) { warning("Can't load picture %s - %s", file_transfer.get_file().get_path(), error.message); Idle.add(get_image_widget.callback); return null; } pixbuf = pixbuf.apply_embedded_orientation(); int max_scaled_height = MAX_HEIGHT * image.scale_factor; if (pixbuf.height > max_scaled_height) { pixbuf = pixbuf.scale_simple((int) ((double) max_scaled_height / pixbuf.height * pixbuf.width), max_scaled_height, Gdk.InterpType.BILINEAR); } int max_scaled_width = MAX_WIDTH * image.scale_factor; if (pixbuf.width > max_scaled_width) { pixbuf = pixbuf.scale_simple(max_scaled_width, (int) ((double) max_scaled_width / pixbuf.width * pixbuf.height), Gdk.InterpType.BILINEAR); } pixbuf = crop_corners(pixbuf, 3 * image.get_scale_factor()); Util.image_set_from_scaled_pixbuf(image, pixbuf); Idle.add(get_image_widget.callback); return image; }); yield; Image image = thread.join(); if (image == null) return null; Util.force_css(image, "* { box-shadow: 0px 0px 2px 0px rgba(0,0,0,0.1); margin: 2px; border-radius: 3px; }"); Builder builder = new Builder.from_resource("/im/dino/Dino/conversation_summary/image_toolbar.ui"); Widget toolbar = builder.get_object("main") as Widget; Util.force_background(toolbar, "rgba(0, 0, 0, 0.5)"); Util.force_css(toolbar, "* { padding: 3px; border-radius: 3px; }"); Label url_label = builder.get_object("url_label") as Label; Util.force_color(url_label, "#eee"); if (file_transfer.file_name != null && file_transfer.file_name != "") { string caption = file_transfer.file_name; url_label.label = caption; } else { url_label.visible = false; } Image open_image = builder.get_object("open_image") as Image; Util.force_css(open_image, "*:not(:hover) { color: #eee; }"); Button open_button = builder.get_object("open_button") as Button; Util.force_css(open_button, "*:hover { background-color: rgba(255,255,255,0.3); border-color: transparent; }"); open_button.clicked.connect(() => { try{ AppInfo.launch_default_for_uri(file_transfer.get_file().get_uri(), null); } catch (Error err) { info("Could not to open file://%s: %s", file_transfer.get_file().get_path(), err.message); } }); Revealer toolbar_revealer = new Revealer() { transition_type=RevealerTransitionType.CROSSFADE, transition_duration=400, visible=true }; toolbar_revealer.add(toolbar); Grid grid = new Grid() { visible=true }; grid.attach(toolbar_revealer, 0, 0, 1, 1); grid.attach(image, 0, 0, 1, 1); EventBox event_box = new EventBox() { margin_top=5, halign=Align.START, visible=true }; event_box.events = EventMask.POINTER_MOTION_MASK; event_box.add(grid); event_box.enter_notify_event.connect(() => { toolbar_revealer.reveal_child = true; return false; }); event_box.leave_notify_event.connect(() => { toolbar_revealer.reveal_child = false; return false; }); return event_box; } private static Gdk.Pixbuf crop_corners(Gdk.Pixbuf pixbuf, double radius = 3) { Cairo.Context ctx = new Cairo.Context(new Cairo.ImageSurface(Cairo.Format.ARGB32, pixbuf.width, pixbuf.height)); Gdk.cairo_set_source_pixbuf(ctx, pixbuf, 0, 0); double degrees = Math.PI / 180.0; ctx.new_sub_path(); ctx.arc(pixbuf.width - radius, radius, radius, -90 * degrees, 0 * degrees); ctx.arc(pixbuf.width - radius, pixbuf.height - radius, radius, 0 * degrees, 90 * degrees); ctx.arc(radius, pixbuf.height - radius, radius, 90 * degrees, 180 * degrees); ctx.arc(radius, radius, radius, 180 * degrees, 270 * degrees); ctx.close_path(); ctx.clip(); ctx.paint(); return Gdk.pixbuf_get_from_surface(ctx.get_target(), 0, 0, pixbuf.width, pixbuf.height); } private Widget get_default_widget(FileTransfer file_transfer) { string icon_name = get_file_icon_name(file_transfer.mime_type); main_box = new Box(Orientation.HORIZONTAL, 10) { halign=Align.FILL, hexpand=true, visible=true }; content_type_image = new Image.from_icon_name(icon_name, IconSize.DND) { opacity=0.5, visible=true }; download_image = new Image.from_icon_name("dino-file-download-symbolic", IconSize.DND) { opacity=0.7, visible=true }; spinner = new Spinner() { visible=true }; EventBox stack_event_box = new EventBox() { visible=true }; image_stack = new Stack() { transition_type = StackTransitionType.CROSSFADE, transition_duration=50, valign=Align.CENTER, visible=true }; image_stack.add_named(download_image, "download_image"); image_stack.add_named(spinner, "spinner"); image_stack.add_named(content_type_image, "content_type_image"); stack_event_box.add(image_stack); main_box.add(stack_event_box); Box right_box = new Box(Orientation.VERTICAL, 0) { hexpand=true, visible=true }; Label name_label = new Label(file_transfer.file_name) { ellipsize=EllipsizeMode.MIDDLE, max_width_chars=1, hexpand=true, xalign=0, yalign=0, visible=true}; right_box.add(name_label); EventBox mime_label_event_box = new EventBox() { visible=true }; mime_label = new Label("") { use_markup=true, xalign=0, yalign=1, visible=true}; mime_label_event_box.add(mime_label); mime_label.get_style_context().add_class("dim-label"); right_box.add(mime_label_event_box); main_box.add(right_box); EventBox event_box = new EventBox() { margin_top=5, width_request=500, halign=Align.START, visible=true }; event_box.get_style_context().add_class("file-box-outer"); event_box.add(main_box); main_box.get_style_context().add_class("file-box"); event_box.enter_notify_event.connect((event) => { pointer_inside = true; Timeout.add(20, () => { if (pointer_inside) { event.get_window().set_cursor(new Cursor.for_display(Gdk.Display.get_default(), CursorType.HAND2)); content_type_image.opacity = 0.7; if (file_transfer.state == FileTransfer.State.NOT_STARTED) { image_stack.set_visible_child_name("download_image"); } } return false; }); return false; }); stack_event_box.enter_notify_event.connect((event) => { pointer_inside = true; return false; }); mime_label_event_box.enter_notify_event.connect((event) => { pointer_inside = true; return false; }); mime_label.enter_notify_event.connect((event) => { pointer_inside = true; return false; }); event_box.leave_notify_event.connect((event) => { pointer_inside = false; Timeout.add(20, () => { if (!pointer_inside) { event.get_window().set_cursor(new Cursor.for_display(Gdk.Display.get_default(), CursorType.XTERM)); content_type_image.opacity = 0.5; if (file_transfer.state == FileTransfer.State.NOT_STARTED) { image_stack.set_visible_child_name("content_type_image"); } } return false; }); return false; }); stack_event_box.leave_notify_event.connect((event) => { pointer_inside = true; return false; }); mime_label_event_box.leave_notify_event.connect((event) => { pointer_inside = true; return false; }); mime_label.leave_notify_event.connect((event) => { pointer_inside = true; return false; }); event_box.button_release_event.connect((event_button) => { switch (file_transfer.state) { case FileTransfer.State.COMPLETE: if (event_button.button == 1) { try{ AppInfo.launch_default_for_uri(file_transfer.get_file().get_uri(), null); } catch (Error err) { print("Tried to open " + file_transfer.get_file().get_path()); } } break; case FileTransfer.State.NOT_STARTED: stream_interactor.get_module(FileManager.IDENTITY).download_file.begin(file_transfer); break; } return false; }); main_box.events = EventMask.POINTER_MOTION_MASK; content_type_image.events = EventMask.POINTER_MOTION_MASK; download_image.events = EventMask.POINTER_MOTION_MASK; spinner.events = EventMask.POINTER_MOTION_MASK; image_stack.events = EventMask.POINTER_MOTION_MASK; right_box.events = EventMask.POINTER_MOTION_MASK; name_label.events = EventMask.POINTER_MOTION_MASK; mime_label.events = EventMask.POINTER_MOTION_MASK; event_box.events = EventMask.POINTER_MOTION_MASK; mime_label.events = EventMask.POINTER_MOTION_MASK; mime_label_event_box.events = EventMask.POINTER_MOTION_MASK; file_transfer.notify["path"].connect(update_file_info); file_transfer.notify["state"].connect(update_file_info); file_transfer.notify["mime-type"].connect(update_file_info); update_file_info.begin(); return event_box; } private async void update_file_info() { if (file_transfer.state == FileTransfer.State.COMPLETE && show_image() && state != State.IMAGE) { this.remove(content); this.add(yield get_image_widget(file_transfer)); state = State.IMAGE; } spinner.active = false; // A hidden spinning spinner still uses CPU. Deactivate asap string? mime_description = file_transfer.mime_type != null ? ContentType.get_description(file_transfer.mime_type) : null; switch (file_transfer.state) { case FileTransfer.State.COMPLETE: mime_label.label = "" + mime_description + ""; image_stack.set_visible_child_name("content_type_image"); break; case FileTransfer.State.IN_PROGRESS: mime_label.label = "" + _("Downloading %sโ€ฆ").printf(get_size_string(file_transfer.size)) + ""; spinner.active = true; image_stack.set_visible_child_name("spinner"); break; case FileTransfer.State.NOT_STARTED: if (mime_description != null) { mime_label.label = "" + _("%s offered: %s").printf(mime_description, get_size_string(file_transfer.size)) + ""; } else if (file_transfer.size != -1) { mime_label.label = "" + _("File offered: %s").printf(get_size_string(file_transfer.size)) + ""; } else { mime_label.label = "" + _("File offered") + ""; } image_stack.set_visible_child_name("content_type_image"); break; case FileTransfer.State.FAILED: mime_label.label = "" + _("File transfer failed") + ""; image_stack.set_visible_child_name("content_type_image"); break; } } private static string get_file_icon_name(string? mime_type) { if (mime_type == null) return "dino-file-symbolic"; string generic_icon_name = ContentType.get_generic_icon_name(mime_type) ?? ""; switch (generic_icon_name) { case "audio-x-generic": return "dino-file-music-symbolic"; case "image-x-generic": return "dino-file-image-symbolic"; case "text-x-generic": return "dino-file-document-symbolic"; case "text-x-generic-template": return "dino-file-document-symbolic"; case "video-x-generic": return "dino-file-video-symbolic"; case "x-office-document": return "dino-file-document-symbolic"; case "x-office-spreadsheet": return "dino-file-table-symbolic"; default: return "dino-file-symbolic"; } } private static string get_size_string(int size) { if (size < 1024) { return @"$(size) B"; } else if (size < 1000 * 1000) { return @"$(size / 1000) kB"; } else if (size < 1000 * 1000 * 1000) { return @"$(size / 1000 / 1000) MB"; } else { return @"$(size / 1000 / 1000 / 1000) GB"; } } private bool show_image() { if (file_transfer.mime_type == null || file_transfer.state != FileTransfer.State.COMPLETE) return false; foreach (PixbufFormat pixbuf_format in Pixbuf.get_formats()) { foreach (string mime_type in pixbuf_format.get_mime_types()) { if (mime_type == file_transfer.mime_type) { return true; } } } return false; } } } dino-0.1.0/main/src/ui/conversation_summary/message_item.vala0000644000000000000000000000000013614354364023064 0ustar rootrootdino-0.1.0/main/src/ui/conversation_summary/subscription_notification.vala0000644000000000000000000000441213614354364025727 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; namespace Dino.Ui.ConversationSummary { public class SubscriptionNotitication : Object { private StreamInteractor stream_interactor; private Conversation conversation; private ConversationView conversation_view; public SubscriptionNotitication(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; stream_interactor.get_module(PresenceManager.IDENTITY).received_subscription_request.connect((jid, account) => { Conversation relevant_conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(jid, account, Conversation.Type.CHAT); stream_interactor.get_module(ConversationManager.IDENTITY).start_conversation(relevant_conversation); if (conversation != null && account.equals(conversation.account) && jid.equals(conversation.counterpart)) { show_notification(); } }); } public void init(Conversation conversation, ConversationView conversation_view) { this.conversation = conversation; this.conversation_view = conversation_view; if (stream_interactor.get_module(PresenceManager.IDENTITY).exists_subscription_request(conversation.account, conversation.counterpart)) { show_notification(); } } private void show_notification() { Box box = new Box(Orientation.HORIZONTAL, 5) { visible=true }; Button accept_button = new Button() { label=_("Accept"), visible=true }; Button deny_button = new Button() { label=_("Deny"), visible=true }; GLib.Application app = GLib.Application.get_default(); accept_button.clicked.connect(() => { app.activate_action("accept-subscription", conversation.id); conversation_view.remove_notification(box); }); deny_button.clicked.connect(() => { app.activate_action("deny-subscription", conversation.id); conversation_view.remove_notification(box); }); box.add(new Label(_("This contact would like to add you to their contact list")) { margin_end=10, visible=true }); box.add(accept_button); box.add(deny_button); conversation_view.add_notification(box); } } } dino-0.1.0/main/src/ui/conversation_titlebar/0000755000000000000000000000000013614354364017700 5ustar rootrootdino-0.1.0/main/src/ui/conversation_titlebar/conversation_titlebar.vala0000644000000000000000000000675313614354364025160 0ustar rootrootusing Gtk; using Gee; using Pango; using Dino.Entities; namespace Dino.Ui { public interface ConversationTitlebar: Widget { public abstract string? subtitle { get; set; } public abstract string? title { get; set; } } public class ConversationTitlebarNoCsd : ConversationTitlebar, Gtk.Box { public string? title { get { return title_label.label; } set { this.title_label.label = value; } } public string? subtitle { get { return subtitle_label.label; } set { this.subtitle_label.label = "" + value + ""; this.subtitle_label.visible = (value != null); } } private Box content_box = new Box(Orientation.HORIZONTAL, 0) { margin=5, margin_start=15, margin_end=5, hexpand=true, visible=true }; private Label title_label = new Label("") { visible=true }; private Label subtitle_label = new Label("") { use_markup=true, ellipsize=EllipsizeMode.END, visible=false }; public GlobalSearchButton search_button = new GlobalSearchButton() { visible = true }; construct { this.add(content_box); Box titles_box = new Box(Orientation.VERTICAL, 0) { valign=Align.CENTER, hexpand=true, visible=true }; content_box.add(titles_box); titles_box.add(title_label); subtitle_label.attributes = new AttrList(); subtitle_label.get_style_context().add_class("dim-label"); titles_box.add(subtitle_label); Box placeholder_box = new Box(Orientation.VERTICAL, 0) { visible=true }; placeholder_box.add(new Label("") { xalign=0, visible=true }); placeholder_box.add(new Label(" ") { use_markup=true, xalign=0, visible=true }); content_box.add(placeholder_box); } public ConversationTitlebarNoCsd() { this.get_style_context().add_class("dino-header-right"); hexpand = true; search_button.set_image(new Gtk.Image.from_icon_name("system-search-symbolic", Gtk.IconSize.MENU) { visible = true }); Application app = GLib.Application.get_default() as Application; foreach(var e in app.plugin_registry.conversation_titlebar_entries) { Plugins.ConversationTitlebarWidget widget = e.get_widget(Plugins.WidgetType.GTK); if (widget != null) { Button gtk_widget = (Gtk.Button)widget; gtk_widget.relief = ReliefStyle.NONE; content_box.add(gtk_widget); } } } } public class ConversationTitlebarCsd : ConversationTitlebar, Gtk.HeaderBar { public new string? title { get { return this.get_title(); } set { base.set_title(value); } } public new string? subtitle { get { return this.get_subtitle(); } set { base.set_subtitle(value); } } public ConversationTitlebarCsd() { this.get_style_context().add_class("dino-right"); show_close_button = true; hexpand = true; Application app = GLib.Application.get_default() as Application; ArrayList widgets = new ArrayList(); foreach(var e in app.plugin_registry.conversation_titlebar_entries) { Plugins.ConversationTitlebarWidget widget = e.get_widget(Plugins.WidgetType.GTK); if (widget != null) { widgets.insert(0, widget); } } foreach (var w in widgets) { Button gtk_widget = (Gtk.Button)w; this.pack_end(gtk_widget); } } } } dino-0.1.0/main/src/ui/conversation_titlebar/menu_entry.vala0000644000000000000000000000324113614354364022732 0ustar rootrootusing Gtk; using Dino.Entities; namespace Dino.Ui { class MenuEntry : Plugins.ConversationTitlebarEntry, Object { public string id { get { return "menu"; } } StreamInteractor stream_interactor; MenuWidget widget; public MenuEntry(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public double order { get { return 0; } } public Plugins.ConversationTitlebarWidget? get_widget(Plugins.WidgetType type) { if (type == Plugins.WidgetType.GTK) { if (widget == null) { widget = new MenuWidget(stream_interactor) { visible=true, sensitive=false }; } return widget; } return null; } } class MenuWidget : Button, Plugins.ConversationTitlebarWidget { private Conversation? conversation; public MenuWidget(StreamInteractor stream_interactor) { image = new Image.from_icon_name("open-menu-symbolic", IconSize.MENU); clicked.connect(() => { ContactDetails.Dialog contact_details_dialog = new ContactDetails.Dialog(stream_interactor, conversation); contact_details_dialog.set_transient_for((Window) get_toplevel()); contact_details_dialog.present(); }); } public new void set_conversation(Conversation conversation) { this.sensitive = true; this.conversation = conversation; if (conversation.type_ == Conversation.Type.GROUPCHAT) { tooltip_text = "Channel details"; } else { tooltip_text = "Conversation details"; } } public new void unset_conversation() { this.sensitive = false; } } } dino-0.1.0/main/src/ui/conversation_titlebar/occupants_entry.vala0000644000000000000000000000333013614354364023764 0ustar rootrootusing Gtk; using Dino.Entities; namespace Dino.Ui { class OccupantsEntry : Plugins.ConversationTitlebarEntry, Object { public string id { get { return "occupants"; } } StreamInteractor stream_interactor; OccupantsWidget widget; public OccupantsEntry(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public double order { get { return 3; } } public Plugins.ConversationTitlebarWidget? get_widget(Plugins.WidgetType type) { if (type == Plugins.WidgetType.GTK) { if (widget == null) { widget = new OccupantsWidget(stream_interactor); } return widget; } return null; } } class OccupantsWidget : MenuButton, Plugins.ConversationTitlebarWidget { private Conversation? conversation; private StreamInteractor stream_interactor; private OccupantMenu.View menu = null; public OccupantsWidget(StreamInteractor stream_interactor) { image = new Image.from_icon_name("system-users-symbolic", IconSize.MENU); tooltip_text = _("Members"); this.stream_interactor = stream_interactor; set_use_popover(true); } public new void set_conversation(Conversation conversation) { this.conversation = conversation; visible = conversation.type_ == Conversation.Type.GROUPCHAT; if (conversation.type_ == Conversation.Type.GROUPCHAT) { OccupantMenu.View new_menu = new OccupantMenu.View(stream_interactor, conversation); set_popover(new_menu); if (menu != null) menu.destroy(); menu = new_menu; } } public new void unset_conversation() { visible = false; } } } dino-0.1.0/main/src/ui/conversation_titlebar/search_entry.vala0000644000000000000000000000165613614354364023243 0ustar rootrootusing Gtk; using Gee; using Dino.Entities; namespace Dino.Ui { public class SearchMenuEntry : Plugins.ConversationTitlebarEntry, Object { public string id { get { return "search"; } } public GlobalSearchButton search_button = new GlobalSearchButton() { tooltip_text=_("Search messages"), visible = true }; public SearchMenuEntry() { search_button.set_image(new Gtk.Image.from_icon_name("system-search-symbolic", Gtk.IconSize.MENU) { visible = true }); } public double order { get { return 1; } } public Plugins.ConversationTitlebarWidget? get_widget(Plugins.WidgetType type) { if (type == Plugins.WidgetType.GTK) { return search_button; } return null; } } public class GlobalSearchButton : Plugins.ConversationTitlebarWidget, Gtk.ToggleButton { public new void set_conversation(Conversation conversation) { } public new void unset_conversation() { } } } dino-0.1.0/main/src/ui/global_search.vala0000644000000000000000000003203113614354364016731 0ustar rootrootusing Gee; using Gtk; using Pango; using Dino.Entities; namespace Dino.Ui { [GtkTemplate (ui = "/im/dino/Dino/global_search.ui")] public class GlobalSearch : Overlay { public signal void selected_item(MessageItem item); private StreamInteractor stream_interactor; private string search = ""; private int loaded_results = -1; private Mutex reloading_mutex = Mutex(); [GtkChild] public SearchEntry search_entry; [GtkChild] public Label entry_number_label; [GtkChild] public ScrolledWindow results_scrolled; [GtkChild] public Box results_box; [GtkChild] public Stack results_empty_stack; [GtkChild] public Frame auto_complete_overlay; [GtkChild] public ListBox auto_complete_list; public GlobalSearch init(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; search_entry.search_changed.connect(() => { set_search(search_entry.text); }); search_entry.notify["text"].connect_after(() => { update_auto_complete(); }); search_entry.notify["cursor-position"].connect_after(() => { update_auto_complete(); }); results_scrolled.vadjustment.notify["value"].connect(() => { if (results_scrolled.vadjustment.upper - (results_scrolled.vadjustment.value + results_scrolled.vadjustment.page_size) < 100) { if (!reloading_mutex.trylock()) return; Gee.List new_messages = stream_interactor.get_module(SearchProcessor.IDENTITY).match_messages(search, loaded_results); if (new_messages.size == 0) { reloading_mutex.unlock(); return; } loaded_results += new_messages.size; append_messages(new_messages); } }); results_scrolled.vadjustment.notify["upper"].connect_after(() => { reloading_mutex.trylock(); reloading_mutex.unlock(); }); event.connect((event) => { if (auto_complete_overlay.visible) { if (event.type == Gdk.EventType.KEY_PRESS && event.key.keyval == Gdk.Key.Up) { var row = auto_complete_list.get_selected_row(); var index = row == null ? -1 : row.get_index() - 1; if (index == -1) index = (int)auto_complete_list.get_children().length() - 1; auto_complete_list.select_row(auto_complete_list.get_row_at_index(index)); return true; } if (event.type == Gdk.EventType.KEY_PRESS && event.key.keyval == Gdk.Key.Down) { var row = auto_complete_list.get_selected_row(); var index = row == null ? 0 : row.get_index() + 1; if (index == auto_complete_list.get_children().length()) index = 0; auto_complete_list.select_row(auto_complete_list.get_row_at_index(index)); return true; } if (event.type == Gdk.EventType.KEY_PRESS && event.key.keyval == Gdk.Key.Tab || event.type == Gdk.EventType.KEY_RELEASE && event.key.keyval == Gdk.Key.Return) { auto_complete_list.get_selected_row().activate(); return true; } } // TODO: Handle cursor movement in results // TODO: Direct all keystrokes to text input return false; }); return this; } private void update_auto_complete() { Gee.List suggestions = stream_interactor.get_module(SearchProcessor.IDENTITY).suggest_auto_complete(search_entry.text, search_entry.cursor_position); auto_complete_overlay.visible = suggestions.size > 0; if (suggestions.size > 0) { auto_complete_list.@foreach((widget) => auto_complete_list.remove(widget)); foreach(SearchSuggestion suggestion in suggestions) { Builder builder = new Builder.from_resource("/im/dino/Dino/search_autocomplete.ui"); AvatarImage avatar = (AvatarImage)builder.get_object("image"); Label label = (Label)builder.get_object("label"); string display_name; if (suggestion.conversation.type_ == Conversation.Type.GROUPCHAT && !suggestion.conversation.counterpart.equals(suggestion.jid) || suggestion.conversation.type_ == Conversation.Type.GROUPCHAT_PM) { display_name = Util.get_participant_display_name(stream_interactor, suggestion.conversation, suggestion.jid); avatar.set_conversation_participant(stream_interactor, suggestion.conversation, suggestion.jid); } else { display_name = Util.get_conversation_display_name(stream_interactor, suggestion.conversation); avatar.set_conversation(stream_interactor, suggestion.conversation); } if (display_name != suggestion.jid.to_string()) { label.set_markup(@"$display_name $(suggestion.jid)"); } else { label.label = display_name; } ListBoxRow row = new ListBoxRow() { visible = true, can_focus = false }; row.add((Widget)builder.get_object("root")); row.activate.connect(() => { handle_suggestion(suggestion); }); auto_complete_list.add(row); } auto_complete_list.select_row(auto_complete_list.get_row_at_index(0)); } } private void handle_suggestion(SearchSuggestion suggestion) { search_entry.move_cursor(MovementStep.LOGICAL_POSITIONS, suggestion.start_index - search_entry.cursor_position, false); search_entry.delete_from_cursor(DeleteType.CHARS, suggestion.end_index - suggestion.start_index); search_entry.insert_at_cursor(suggestion.completion + " "); } private void clear_search() { results_box.@foreach((widget) => { widget.destroy(); }); } private void set_search(string search) { clear_search(); this.search = search; if (get_keywords(search).is_empty) { results_empty_stack.set_visible_child_name("empty"); return; } Gee.List messages = stream_interactor.get_module(SearchProcessor.IDENTITY).match_messages(search); if (messages.size == 0) { results_empty_stack.set_visible_child_name("no-result"); } else { results_empty_stack.set_visible_child_name("results"); int match_count = messages.size < 10 ? messages.size : stream_interactor.get_module(SearchProcessor.IDENTITY).count_match_messages(search); entry_number_label.label = "" + _("%i search results").printf(match_count) + ""; loaded_results += messages.size; append_messages(messages); } } private void append_messages(Gee.List messages) { foreach (MessageItem item in messages) { Gee.List before_message = stream_interactor.get_module(MessageStorage.IDENTITY).get_messages_before_message(item.conversation, item.message.local_time, item.message.id, 1); Gee.List after_message = stream_interactor.get_module(MessageStorage.IDENTITY).get_messages_after_message(item.conversation, item.message.local_time, item.message.id, 1); Box context_box = new Box(Orientation.VERTICAL, 5) { visible=true }; if (before_message != null && before_message.size > 0) { context_box.add(get_context_message_widget(before_message.first())); } Widget match_widget = get_match_message_widget(item); context_box.add(match_widget); if (after_message != null && after_message.size > 0) { context_box.add(get_context_message_widget(after_message.first())); } Label date_label = new Label(ConversationSummary.ItemMetaDataHeader.get_relative_time(item.display_time)) { xalign=0, visible=true }; date_label.get_style_context().add_class("dim-label"); string display_name = Util.get_conversation_display_name(stream_interactor, item.conversation); string title = item.message.type_ == Message.Type.GROUPCHAT ? _("In %s").printf(display_name) : _("With %s").printf(display_name); Box header_box = new Box(Orientation.HORIZONTAL, 10) { margin_start=7, visible=true }; header_box.add(new Label(@"$(Markup.escape_text(title))") { ellipsize=EllipsizeMode.END, xalign=0, use_markup=true, visible=true }); header_box.add(date_label); Box result_box = new Box(Orientation.VERTICAL, 7) { visible=true }; result_box.add(header_box); result_box.add(context_box); results_box.add(result_box); } } private Widget get_match_message_widget(MessageItem item) { Grid grid = get_skeleton(item); grid.margin_top = 3; grid.margin_bottom = 3; string text = item.message.body.replace("\n", "").replace("\r", ""); if (text.length > 200) { int index = text.index_of(search); if (index + search.length <= 100) { text = text.substring(0, 150) + " โ€ฆ " + text.substring(text.length - 50, 50); } else if (index >= text.length - 100) { text = text.substring(0, 50) + " โ€ฆ " + text.substring(text.length - 150, 150); } else { text = text.substring(0, 25) + " โ€ฆ " + text.substring(index - 50, 50) + text.substring(index, 100) + " โ€ฆ " + text.substring(text.length - 25, 25); } } Label label = new Label("") { use_markup=true, xalign=0, selectable=true, wrap=true, wrap_mode=Pango.WrapMode.WORD_CHAR, vexpand=true, visible=true }; // Build regex containing all keywords string regex_str = "("; Gee.List keywords = get_keywords(Regex.escape_string(search.down())); bool first = true; foreach (string keyword in keywords) { if (first) { first = false; } else { regex_str += "|"; } regex_str += "\\b" + keyword; } regex_str += ")"; // Color the keywords string markup_text = ""; try { Regex highlight_regex = new Regex(regex_str, RegexCompileFlags.CASELESS); MatchInfo match_info; highlight_regex.match(text, 0, out match_info); int last_end = 0; for (; match_info.matches(); match_info.next()) { int start, end; match_info.fetch_pos(0, out start, out end); markup_text += Markup.escape_text(text[last_end:start]) + "" + Markup.escape_text(text[start:end]) + ""; last_end = end; } markup_text += Markup.escape_text(text[last_end:text.length]); } catch (RegexError e) { assert_not_reached(); } label.label = markup_text; grid.attach(label, 1, 1, 1, 1); Button button = new Button() { relief=ReliefStyle.NONE, visible=true }; button.clicked.connect(() => { selected_item(item); }); button.add(grid); return button; } private Grid get_context_message_widget(MessageItem item) { Grid grid = get_skeleton(item); grid.margin_start = 7; Label label = new Label(item.message.body.replace("\n", "").replace("\r", "")) { ellipsize=EllipsizeMode.MIDDLE, xalign=0, visible=true }; grid.attach(label, 1, 1, 1, 1); grid.opacity = 0.55; return grid; } private Grid get_skeleton(MessageItem item) { AvatarImage image = new AvatarImage() { height=32, width=32, margin_end=7, valign=Align.START, visible=true, allow_gray = false }; image.set_conversation_participant(stream_interactor, item.conversation, item.jid); Grid grid = new Grid() { row_homogeneous=false, visible=true }; grid.attach(image, 0, 0, 1, 2); string display_name = Util.get_participant_display_name(stream_interactor, item.conversation, item.jid); string color = Util.get_name_hex_color(stream_interactor, item.message.account, item.jid, false); // TODO Util.is_dark_theme(name_label) Label name_label = new Label("") { use_markup=true, xalign=0, visible=true }; name_label.label = @"$display_name"; grid.attach(name_label, 1, 0, 1, 1); return grid; } private static Gee.List get_keywords(string search_string) { Gee.List ret = new ArrayList(); foreach (string search in search_string.split(" ")) { bool is_filter = search.has_prefix("from:") || search.has_prefix("in:") || search.has_prefix("with:"); if (!is_filter && search != "") { ret.add(search); } } return ret; } } } dino-0.1.0/main/src/ui/manage_accounts/0000755000000000000000000000000013614354364016427 5ustar rootrootdino-0.1.0/main/src/ui/manage_accounts/account_row.vala0000644000000000000000000000247513614354364021627 0ustar rootrootusing Gtk; using Dino.Entities; namespace Dino.Ui.ManageAccounts { [GtkTemplate (ui = "/im/dino/Dino/manage_accounts/account_row.ui")] public class AccountRow : Gtk.ListBoxRow { [GtkChild] public AvatarImage image; [GtkChild] public Label jid_label; [GtkChild] public Image icon; public Account account; private StreamInteractor stream_interactor; public AccountRow(StreamInteractor stream_interactor, Account account) { this.stream_interactor = stream_interactor; this.account = account; image.set_conversation(stream_interactor, new Conversation(account.bare_jid, account, Conversation.Type.CHAT)); jid_label.set_label(account.bare_jid.to_string()); stream_interactor.connection_manager.connection_error.connect((account, error) => { if (account.equals(this.account)) { update_warning_icon(); } }); stream_interactor.connection_manager.connection_state_changed.connect((account, state) => { if (account.equals(this.account)) { update_warning_icon(); } }); } private void update_warning_icon() { ConnectionManager.ConnectionError? error = stream_interactor.connection_manager.get_error(account); icon.visible = (error != null); } } } dino-0.1.0/main/src/ui/manage_accounts/add_account_dialog.vala0000644000000000000000000004164713614354364023073 0ustar rootrootusing Gee; using Gtk; using Pango; using Dino.Entities; using Xmpp; namespace Dino.Ui.ManageAccounts { [GtkTemplate (ui = "/im/dino/Dino/manage_accounts/add_account_dialog.ui")] public class AddAccountDialog : Gtk.Dialog { public signal void added(Account account); [GtkChild] private Stack stack; [GtkChild] private Revealer notification_revealer; [GtkChild] private Label notification_label; // Sign in - JID [GtkChild] private Box sign_in_jid_box; [GtkChild] private Label sign_in_jid_error_label; [GtkChild] private Entry jid_entry; [GtkChild] private Stack sign_in_jid_continue_stack; [GtkChild] private Button sign_in_jid_continue_button; [GtkChild] private Button sign_in_jid_serverlist_button; // Sign in - TLS error [GtkChild] private Box sign_in_tls_box; [GtkChild] private Label sign_in_tls_label; [GtkChild] private Stack sign_in_password_continue_stack; [GtkChild] private Button sign_in_tls_back_button; // Sign in - Password [GtkChild] private Box sign_in_password_box; [GtkChild] private Label sign_in_password_title; [GtkChild] private Label sign_in_password_error_label; [GtkChild] private Entry password_entry; [GtkChild] private Button sign_in_password_continue_button; [GtkChild] private Button sign_in_password_back_button; // Select Server [GtkChild] private Box create_account_box; [GtkChild] private Button login_button; [GtkChild] private Stack select_server_continue_stack; [GtkChild] private Button select_server_continue; [GtkChild] private Label register_form_continue_label; [GtkChild] private ListBox server_list_box; [GtkChild] private Entry server_entry; // Register Form [GtkChild] private Box register_box; [GtkChild] private Label register_title; [GtkChild] private Box form_box; [GtkChild] private Button register_form_back; [GtkChild] private Stack register_form_continue_stack; [GtkChild] private Button register_form_continue; // Success [GtkChild] private Box success_box; [GtkChild] private Label success_description; [GtkChild] private Button success_continue_button; private static string[] server_list = new string[]{ "5222.de", "jabber.fr", "movim.eu", "yax.im" }; private StreamInteractor stream_interactor; private Database db; private HashMap list_box_jids = new HashMap(); private Jid? server_jid = null; private Jid? login_jid = null; private Xep.InBandRegistration.Form? form = null; public AddAccountDialog(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; this.db = db; this.title = _("Add Account"); // Sign in - Jid Util.force_error_color(sign_in_jid_error_label); jid_entry.changed.connect(on_jid_entry_changed); sign_in_jid_continue_button.clicked.connect(on_sign_in_jid_continue_button_clicked); sign_in_jid_serverlist_button.clicked.connect(show_select_server); // Sign in - TLS error sign_in_tls_back_button.clicked.connect(show_sign_in_jid); // Sign in - Password Util.force_error_color(sign_in_password_error_label); password_entry.changed.connect(() => { sign_in_password_continue_button.set_sensitive(password_entry.text.length > 0); }); sign_in_password_continue_button.clicked.connect(on_sign_in_password_continue_button_clicked); sign_in_password_back_button.clicked.connect(show_sign_in_jid); // Select Server server_entry.changed.connect(() => { try { Jid jid = new Jid(server_entry.text); select_server_continue.sensitive = jid != null && jid.localpart == null && jid.resourcepart == null; } catch (InvalidJidError e) { select_server_continue.sensitive = false; } }); select_server_continue.clicked.connect(on_select_server_continue); login_button.clicked.connect(show_sign_in_jid); foreach (string server in server_list) { ListBoxRow list_box_row = new ListBoxRow() { visible=true }; list_box_row.add(new Label(server) { xalign=0, margin=3, margin_start=7, margin_end=7, visible=true }); list_box_jids[list_box_row] = server; server_list_box.add(list_box_row); } // Register Form register_form_continue.clicked.connect(on_register_form_continue_clicked); register_form_back.clicked.connect(show_select_server); // Success success_continue_button.clicked.connect(() => close()); show_sign_in_jid(); } private void show_sign_in_jid() { sign_in_jid_box.visible = true; jid_entry.grab_focus(); stack.visible_child_name = "login_jid"; sign_in_tls_box.visible = false; sign_in_password_box.visible = false; create_account_box.visible = false; register_box.visible = false; success_box.visible = false; set_default(sign_in_jid_continue_button); sign_in_jid_error_label.label = ""; jid_entry.sensitive = true; animate_window_resize(sign_in_jid_box); } private void show_tls_error() { sign_in_tls_box.visible = true; stack.visible_child_name = "tls_error"; sign_in_jid_box.visible = false; sign_in_password_box.visible = false; create_account_box.visible = false; register_box.visible = false; success_box.visible = false; } private void show_sign_in_password() { sign_in_password_box.visible = true; password_entry.grab_focus(); stack.visible_child_name = "login_password"; sign_in_jid_box.visible = false; sign_in_tls_box.visible = false; create_account_box.visible = false; register_box.visible = false; success_box.visible = false; set_default(sign_in_password_continue_button); sign_in_password_error_label.label = ""; sign_in_password_title.label = _("Sign in to %s").printf(login_jid.to_string()); animate_window_resize(sign_in_password_box); } private void show_select_server() { server_entry.text = ""; server_entry.grab_focus(); set_default(select_server_continue); server_list_box.row_activated.disconnect(on_server_list_row_activated); server_list_box.unselect_all(); server_list_box.row_activated.connect(on_server_list_row_activated); create_account_box.visible = true; stack.visible_child_name = "server"; sign_in_jid_box.visible = false; sign_in_tls_box.visible = false; register_box.visible = false; success_box.visible = false; animate_window_resize(create_account_box); } private void show_register_form() { register_box.visible = true; stack.visible_child_name = "form"; sign_in_jid_box.visible = false; sign_in_tls_box.visible = false; sign_in_password_box.visible = false; create_account_box.visible = false; success_box.visible = false; set_default(register_form_continue); animate_window_resize(register_box); } private void show_success(Account account) { success_box.visible = true; stack.visible_child_name = "success"; sign_in_jid_box.visible = false; sign_in_tls_box.visible = false; sign_in_password_box.visible = false; create_account_box.visible = false; register_box.visible = false; success_description.label = _("You can now start using %s").printf("" + Markup.escape_text(account.bare_jid.to_string()) + ""); set_default(success_continue_button); } private void on_jid_entry_changed() { try { login_jid = new Jid(jid_entry.text); if (login_jid.localpart != null && login_jid.resourcepart == null) { sign_in_jid_continue_button.sensitive = true; jid_entry.secondary_icon_name = null; } else { sign_in_jid_continue_button.sensitive = false; } } catch (InvalidJidError e) { sign_in_jid_continue_button.sensitive = false; } } private async void on_sign_in_jid_continue_button_clicked() { try { login_jid = new Jid(jid_entry.text); jid_entry.sensitive = false; sign_in_tls_label.label = ""; sign_in_jid_error_label.label = ""; sign_in_jid_continue_button.sensitive = false; sign_in_jid_continue_stack.visible_child_name = "spinner"; Register.ServerAvailabilityReturn server_status = yield Register.check_server_availability(login_jid); sign_in_jid_continue_stack.visible_child_name = "label"; sign_in_jid_continue_button.sensitive = true; if (server_status.available) { show_sign_in_password(); } else { jid_entry.sensitive = true; if (server_status.error_flags != null) { string error_desc = "The server could not prove that it is %s.".printf("" + login_jid.domainpart + ""); if (TlsCertificateFlags.UNKNOWN_CA in server_status.error_flags) { error_desc += " " + "Its security certificate is not trusted by your computer's operating system."; } else if (TlsCertificateFlags.BAD_IDENTITY in server_status.error_flags) { error_desc += " " + "Its security certificate is issued to another domain."; } else if (TlsCertificateFlags.NOT_ACTIVATED in server_status.error_flags) { error_desc += " " + "Its security certificate will only become valid in the future."; } else if (TlsCertificateFlags.EXPIRED in server_status.error_flags) { error_desc += " " + "Its security certificate is expired."; } sign_in_tls_label.label = error_desc; show_tls_error(); } else { sign_in_jid_error_label.label = _("Could not connect to %s").printf(login_jid.domainpart); } } } catch (InvalidJidError e) { warning("Invalid address from interface allowed login: %s", e.message); sign_in_jid_error_label.label = _("Invalid address"); } } private async void on_sign_in_password_continue_button_clicked() { string password = password_entry.text; Account account = new Account(login_jid, null, password, null); sign_in_password_continue_stack.visible_child_name = "spinner"; ConnectionManager.ConnectionError.Source? error = yield stream_interactor.get_module(Register.IDENTITY).add_check_account(account); sign_in_password_continue_stack.visible_child_name = "label"; if (error != null) { switch (error) { case ConnectionManager.ConnectionError.Source.SASL: sign_in_password_error_label.label = _("Wrong username or password"); break; default: sign_in_password_error_label.label = _("Something went wrong"); break; } } else { add_activate_account(account); show_success(account); } } private void on_select_server_continue() { try { server_jid = new Jid(server_entry.text); request_show_register_form.begin(); } catch (InvalidJidError e) { warning("Invalid address from interface allowed server: %s", e.message); display_notification(_("Invalid address")); } } private void on_server_list_row_activated(ListBox box, ListBoxRow row) { try { server_jid = new Jid(list_box_jids[row]); request_show_register_form.begin(); } catch (InvalidJidError e) { warning("Invalid address from selected server: %s", e.message); display_notification(_("Invalid address")); } } private async void request_show_register_form() { select_server_continue_stack.visible_child_name = "spinner"; form = yield Register.get_registration_form(server_jid); if (select_server_continue_stack == null) { return; } select_server_continue_stack.visible_child_name = "label"; if (form != null) { set_register_form(server_jid, form); show_register_form(); } else { display_notification(_("No response from server")); } } private void set_register_form(Jid server, Xep.InBandRegistration.Form form) { form_box.foreach((widget) => { widget.destroy(); }); register_title.label = _("Register on %s").printf(server.to_string()); if (form.oob != null) { form_box.add(new Label(_("The server requires to sign up through a website")){ visible=true } ); form_box.add(new Label(@"$(form.oob)") { use_markup=true, visible=true }); register_form_continue_label.label = _("Open Registration"); register_form_continue.visible = true; register_form_continue.grab_focus(); } else if (form.fields.size > 0) { foreach (Xep.DataForms.DataForm.Field field in form.fields) { Widget? field_widget = Util.get_data_form_field_widget(field); if (field.label != null && field.label != "" && field_widget != null) { form_box.add(new Label(field.label) { xalign=0, margin_top=7, visible=true }); form_box.add(field_widget); } } register_form_continue.visible = true; register_form_continue_label.label = _("Register"); } else { form_box.add(new Label(_("Check %s for information on how to sign up").printf(@"$(server)")) { use_markup=true, visible=true }); register_form_continue.visible = false; } } private async void on_register_form_continue_clicked() { notification_revealer.set_reveal_child(false); // Button is opening a registration website if (form.oob != null) { try { AppInfo.launch_default_for_uri(form.oob, null); } catch (Error e) { } show_sign_in_jid(); return; } register_form_continue_stack.visible_child_name = "spinner"; string? error = yield Register.submit_form(server_jid, form); if (register_form_continue_stack == null) { return; } register_form_continue_stack.visible_child_name = "label"; if (error == null) { string? username = null, password = null; foreach (Xep.DataForms.DataForm.Field field in form.fields) { switch (field.var) { case "username": username = field.get_value_string(); break; case "password": password = field.get_value_string(); break; } } try { Account account = new Account(new Jid.components(username, server_jid.domainpart, null), null, password, null); add_activate_account(account); show_success(account); } catch (InvalidJidError e) { warning("Invalid address from components of registration: %s", e.message); display_notification(_("Invalid address")); } } else { display_notification(error); } } private void display_notification(string text) { notification_label.label = text; notification_revealer.set_reveal_child(true); Timeout.add_seconds(5, () => { notification_revealer.set_reveal_child(false); return false; }); } private void add_activate_account(Account account) { account.enabled = true; account.persist(db); stream_interactor.connect_account(account); added(account); } private void animate_window_resize(Widget widget) { // TODO code duplication int def_height, curr_width, curr_height; get_size(out curr_width, out curr_height); widget.get_preferred_height(null, out def_height); def_height += 5; int difference = def_height - curr_height; Timer timer = new Timer(); Timeout.add((int) (stack.transition_duration / 30), () => { ulong microsec; timer.elapsed(out microsec); ulong millisec = microsec / 1000; double partial = double.min(1, (double) millisec / stack.transition_duration); resize(curr_width, (int) (curr_height + difference * partial)); return millisec < stack.transition_duration; }); } } } dino-0.1.0/main/src/ui/manage_accounts/dialog.vala0000644000000000000000000002371213614354364020540 0ustar rootrootusing Gdk; using Gee; using Gtk; using Markup; using Dino.Entities; using Xmpp; namespace Dino.Ui.ManageAccounts { [GtkTemplate (ui = "/im/dino/Dino/manage_accounts/dialog.ui")] public class Dialog : Gtk.Dialog { public signal void account_enabled(Account account); public signal void account_disabled(Account account); [GtkChild] public Stack main_stack; [GtkChild] public ListBox account_list; [GtkChild] public Button no_accounts_add; [GtkChild] public ToolButton add_account_button; [GtkChild] public ToolButton remove_account_button; [GtkChild] public AvatarImage image; [GtkChild] public Button image_button; [GtkChild] public Label jid_label; [GtkChild] public Label state_label; [GtkChild] public Switch active_switch; [GtkChild] public Util.EntryLabelHybrid password_hybrid; [GtkChild] public Util.EntryLabelHybrid alias_hybrid; [GtkChild] public Grid settings_list; private ArrayList plugin_widgets = new ArrayList(); private Database db; private StreamInteractor stream_interactor; private Account? selected_account; construct { Util.force_error_color(state_label, ".is_error"); account_list.row_selected.connect(on_account_list_row_selected); add_account_button.clicked.connect(show_add_account_dialog); no_accounts_add.clicked.connect(show_add_account_dialog); remove_account_button.clicked.connect(() => { AccountRow? account_row = account_list.get_selected_row() as AccountRow; if (selected_account != null) remove_account(account_row); }); image_button.clicked.connect(show_select_avatar); alias_hybrid.entry.key_release_event.connect(() => { selected_account.alias = alias_hybrid.text; return false; }); password_hybrid.entry.key_release_event.connect(() => { selected_account.password = password_hybrid.text; return false; }); Util.LabelHybridGroup label_hybrid_group = new Util.LabelHybridGroup(); label_hybrid_group.add(alias_hybrid); label_hybrid_group.add(password_hybrid); main_stack.set_visible_child_name("no_accounts"); int row_index = 4; int16 default_top_padding = new Gtk.Button().get_style_context().get_padding(Gtk.StateFlags.NORMAL).top + 1; Application app = GLib.Application.get_default() as Application; foreach (var e in app.plugin_registry.account_settings_entries) { Plugins.AccountSettingsWidget widget = e.get_widget(Plugins.WidgetType.GTK); plugin_widgets.add(widget); Label label = new Label(e.name) { xalign=1, yalign=0, visible=true }; label.get_style_context().add_class("dim-label"); label.margin_top = e.label_top_padding == -1 ? default_top_padding : e.label_top_padding; settings_list.attach(label, 0, row_index); if (widget is Widget) { Widget gtkw = (Widget) widget; plugin_widgets.add(widget); gtkw.visible = true; settings_list.attach(gtkw, 1, row_index, 2); } else { // TODO } row_index++; } } public Dialog(StreamInteractor stream_interactor, Database db) { Object(use_header_bar : Util.use_csd() ? 1 : 0); this.db = db; this.stream_interactor = stream_interactor; foreach (Account account in db.get_accounts()) { add_account(account); } stream_interactor.get_module(AvatarManager.IDENTITY).received_avatar.connect(on_received_avatar); stream_interactor.connection_manager.connection_error.connect((account, error) => { if (account.equals(selected_account)) { update_status_label(account); } }); stream_interactor.connection_manager.connection_state_changed.connect((account, state) => { if (account.equals(selected_account)) { update_status_label(account); } }); if (account_list.get_row_at_index(0) != null) account_list.select_row(account_list.get_row_at_index(0)); } public AccountRow add_account(Account account) { AccountRow account_item = new AccountRow (stream_interactor, account); account_list.add(account_item); main_stack.set_visible_child_name("accounts_exist"); return account_item; } private void show_add_account_dialog() { AddAccountDialog add_account_dialog = new AddAccountDialog(stream_interactor, db); add_account_dialog.set_transient_for(this); add_account_dialog.added.connect((account) => { AccountRow account_item = add_account(account); account_list.select_row(account_item); account_list.queue_draw(); }); add_account_dialog.present(); } private void remove_account(AccountRow account_item) { Gtk.MessageDialog msg = new Gtk.MessageDialog ( this, Gtk.DialogFlags.DESTROY_WITH_PARENT | Gtk.DialogFlags.MODAL, Gtk.MessageType.WARNING, Gtk.ButtonsType.OK_CANCEL, _("Remove account %s?"), account_item.jid_label.get_text()); msg.secondary_text = "You won't be able to access your conversation history anymore."; // TODO remove history! Button ok_button = msg.get_widget_for_response(ResponseType.OK) as Button; ok_button.label = _("Remove"); ok_button.get_style_context().add_class("destructive-action"); if (msg.run() == Gtk.ResponseType.OK) { account_item.destroy(); if (account_item.account.enabled) account_disabled(account_item.account); account_item.account.remove(); if (account_list.get_row_at_index(0) != null) { account_list.select_row(account_list.get_row_at_index(0)); } else { main_stack.set_visible_child_name("no_accounts"); } } msg.close(); } private void on_account_list_row_selected(ListBoxRow? row) { AccountRow? account_item = row as AccountRow; if (account_item != null) { selected_account = account_item.account; populate_grid_data(account_item.account); } } private void show_select_avatar() { PreviewFileChooserNative chooser = new PreviewFileChooserNative(_("Select avatar"), this, FileChooserAction.OPEN, _("Select"), _("Cancel")); FileFilter filter = new FileFilter(); foreach (PixbufFormat pixbuf_format in Pixbuf.get_formats()) { foreach (string mime_type in pixbuf_format.get_mime_types()) { filter.add_mime_type(mime_type); } } filter.set_filter_name(_("Images")); chooser.add_filter(filter); filter = new FileFilter(); filter.set_filter_name(_("All files")); filter.add_pattern("*"); chooser.add_filter(filter); if (chooser.run() == Gtk.ResponseType.ACCEPT) { string uri = chooser.get_filename(); stream_interactor.get_module(AvatarManager.IDENTITY).publish(selected_account, uri); } } private bool change_account_state(bool state) { selected_account.enabled = state; if (state) { account_enabled(selected_account); } else { account_disabled(selected_account); } return false; } private void on_received_avatar(Pixbuf pixbuf, Jid jid, Account account) { if (selected_account.equals(account) && jid.equals(account.bare_jid)) { image.set_conversation(stream_interactor, new Conversation(account.bare_jid, account, Conversation.Type.CHAT)); } } private void populate_grid_data(Account account) { active_switch.state_set.disconnect(change_account_state); image.set_conversation(stream_interactor, new Conversation(account.bare_jid, account, Conversation.Type.CHAT)); active_switch.set_active(account.enabled); jid_label.label = account.bare_jid.to_string(); alias_hybrid.text = account.alias ?? ""; password_hybrid.entry.input_purpose = InputPurpose.PASSWORD; password_hybrid.text = account.password; update_status_label(account); active_switch.state_set.connect(change_account_state); foreach(Plugins.AccountSettingsWidget widget in plugin_widgets) { widget.set_account(account); } } private void update_status_label(Account account) { state_label.label = ""; ConnectionManager.ConnectionError? error = stream_interactor.connection_manager.get_error(account); if (error != null) { state_label.label = get_connection_error_description(error); state_label.get_style_context().add_class("is_error"); } else { ConnectionManager.ConnectionState state = stream_interactor.connection_manager.get_state(account); switch (state) { case ConnectionManager.ConnectionState.CONNECTING: state_label.label = _("Connectingโ€ฆ"); break; case ConnectionManager.ConnectionState.CONNECTED: state_label.label = _("Connected"); break; case ConnectionManager.ConnectionState.DISCONNECTED: state_label.label = _("Disconnected"); break; } state_label.get_style_context().remove_class("is_error"); } } private string get_connection_error_description(ConnectionManager.ConnectionError error) { switch (error.source) { case ConnectionManager.ConnectionError.Source.SASL: return _("Wrong password"); case ConnectionManager.ConnectionError.Source.TLS: return _("Invalid TLS certificate"); } if (error.identifier != null) { return _("Error") + ": " + error.identifier; } else { return _("Error"); } } } } dino-0.1.0/main/src/ui/notifications.vala0000644000000000000000000002040113614354364017013 0ustar rootrootusing Gee; using Dino.Entities; using Xmpp; namespace Dino.Ui { public class Notifications : Object { public signal void conversation_selected(Conversation conversation); private StreamInteractor stream_interactor; private HashMap notifications = new HashMap(Conversation.hash_func, Conversation.equals_func); private Set? active_conversation_ids = null; private Set? active_ids = new HashSet(); public Notifications(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; stream_interactor.get_module(ChatInteraction.IDENTITY).focused_in.connect((focused_conversation) => { if (active_conversation_ids == null) { Gee.List conversations = stream_interactor.get_module(ConversationManager.IDENTITY).get_active_conversations(); foreach (Conversation conversation in conversations) { GLib.Application.get_default().withdraw_notification(conversation.id.to_string()); } active_conversation_ids = new HashSet(); } else { foreach (string id in active_conversation_ids) { GLib.Application.get_default().withdraw_notification(id); } active_conversation_ids.clear(); } string subscription_id = focused_conversation.id.to_string() + "-subscription"; if (active_ids.contains(subscription_id)) { GLib.Application.get_default().withdraw_notification(subscription_id); } }); } public void start() { stream_interactor.get_module(NotificationEvents.IDENTITY).notify_content_item.connect((content_item, conversation) => notify_content_item.begin(content_item, conversation)); stream_interactor.get_module(NotificationEvents.IDENTITY).notify_subscription_request.connect(notify_subscription_request); stream_interactor.get_module(NotificationEvents.IDENTITY).notify_connection_error.connect(notify_connection_error); stream_interactor.get_module(NotificationEvents.IDENTITY).notify_muc_invite.connect(on_invite_received); } private async void notify_content_item(ContentItem content_item, Conversation conversation) { if (!notifications.has_key(conversation)) { notifications[conversation] = new Notification(""); notifications[conversation].set_default_action_and_target_value("app.open-conversation", new Variant.int32(conversation.id)); } string display_name = Util.get_conversation_display_name(stream_interactor, conversation); string text = ""; switch (content_item.type_) { case MessageItem.TYPE: Message message = (content_item as MessageItem).message; text = message.body; break; case FileItem.TYPE: FileItem file_item = content_item as FileItem; FileTransfer transfer = file_item.file_transfer; bool file_is_image = transfer.mime_type != null && transfer.mime_type.has_prefix("image"); if (transfer.direction == Message.DIRECTION_SENT) { text = file_is_image ? _("Image sent") : _("File sent"); } else { text = file_is_image ? _("Image received") : _("File received"); } break; } if (stream_interactor.get_module(MucManager.IDENTITY).is_groupchat(conversation.counterpart, conversation.account)) { string muc_occupant = Util.get_participant_display_name(stream_interactor, conversation, content_item.jid); text = @"$muc_occupant: $text"; } notifications[conversation].set_title(display_name); notifications[conversation].set_body(text); try { Cairo.ImageSurface conversation_avatar = (yield Util.get_conversation_avatar_drawer(stream_interactor, conversation)).size(40, 40).draw_image_surface(); notifications[conversation].set_icon(get_pixbuf_icon(conversation_avatar)); } catch (Error e) { } GLib.Application.get_default().send_notification(conversation.id.to_string(), notifications[conversation]); if (active_conversation_ids != null) { active_conversation_ids.add(conversation.id.to_string()); } // Don't set urgency hint in GNOME, produces "Window is active" notification var desktop_env = Environment.get_variable("XDG_CURRENT_DESKTOP"); if (desktop_env == null || !desktop_env.down().contains("gnome")) { var app = (GLib.Application.get_default() as Application); if (app.active_window != null) { app.active_window.urgency_hint = true; } } } private async void notify_subscription_request(Conversation conversation) { Notification notification = new Notification(_("Subscription request")); notification.set_body(conversation.counterpart.to_string()); try { Cairo.ImageSurface jid_avatar = (yield Util.get_conversation_avatar_drawer(stream_interactor, conversation)).size(40, 40).draw_image_surface(); notification.set_icon(get_pixbuf_icon(jid_avatar)); } catch (Error e) { } notification.set_default_action_and_target_value("app.open-conversation", new Variant.int32(conversation.id)); notification.add_button_with_target_value(_("Accept"), "app.accept-subscription", conversation.id); notification.add_button_with_target_value(_("Deny"), "app.deny-subscription", conversation.id); GLib.Application.get_default().send_notification(conversation.id.to_string() + "-subscription", notification); active_ids.add(conversation.id.to_string() + "-subscription"); } private void notify_connection_error(Account account, ConnectionManager.ConnectionError error) { Notification notification = new Notification(_("Could not connect to %s").printf(account.bare_jid.domainpart)); switch (error.source) { case ConnectionManager.ConnectionError.Source.SASL: notification.set_body("Wrong password"); break; case ConnectionManager.ConnectionError.Source.TLS: notification.set_body("Invalid TLS certificate"); break; } GLib.Application.get_default().send_notification(account.id.to_string() + "-connection-error", notification); } private async void on_invite_received(Account account, Jid room_jid, Jid from_jid, string? password, string? reason) { Conversation direct_conversation = new Conversation(from_jid, account, Conversation.Type.CHAT); string display_name = Util.get_participant_display_name(stream_interactor, direct_conversation, from_jid); string display_room = room_jid.bare_jid.to_string(); Notification notification = new Notification(_("Invitation to %s").printf(display_room)); string body = _("%s invited you to %s").printf(display_name, display_room); notification.set_body(body); try { Cairo.ImageSurface jid_avatar = (yield Util.get_conversation_avatar_drawer(stream_interactor, direct_conversation)).size(40, 40).draw_image_surface(); notification.set_icon(get_pixbuf_icon(jid_avatar)); } catch (Error e) { } Conversation group_conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(room_jid, account, Conversation.Type.GROUPCHAT); notification.set_default_action_and_target_value("app.open-muc-join", new Variant.int32(group_conversation.id)); notification.add_button_with_target_value(_("Deny"), "app.deny-invite", group_conversation.id); notification.add_button_with_target_value(_("Accept"), "app.open-muc-join", group_conversation.id); GLib.Application.get_default().send_notification(null, notification); } private Icon get_pixbuf_icon(Cairo.ImageSurface surface) throws Error { Gdk.Pixbuf avatar = Gdk.pixbuf_get_from_surface(surface, 0, 0, surface.get_width(), surface.get_height()); uint8[] buffer; avatar.save_to_buffer(out buffer, "png"); return new BytesIcon(new Bytes(buffer)); } } } dino-0.1.0/main/src/ui/occupant_menu/0000755000000000000000000000000013614354364016140 5ustar rootrootdino-0.1.0/main/src/ui/occupant_menu/list.vala0000644000000000000000000001472413614354364017770 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; using Xmpp; namespace Dino.Ui.OccupantMenu{ [GtkTemplate (ui = "/im/dino/Dino/occupant_list.ui")] public class List : Box { public signal void conversation_selected(Conversation? conversation); private StreamInteractor stream_interactor; [GtkChild] public ListBox list_box; [GtkChild] private SearchEntry search_entry; private Conversation conversation; private string[]? filter_values; private HashMap rows = new HashMap(Jid.hash_func, Jid.equals_func); public List(StreamInteractor stream_interactor, Conversation conversation) { this.stream_interactor = stream_interactor; list_box.set_header_func(header); list_box.set_sort_func(sort); list_box.set_filter_func(filter); search_entry.search_changed.connect(refilter); stream_interactor.get_module(PresenceManager.IDENTITY).show_received.connect(on_show_received); stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.connect(on_updated_roster_item); initialize_for_conversation(conversation); } public void initialize_for_conversation(Conversation conversation) { this.conversation = conversation; Gee.List? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_occupants(conversation.counterpart, conversation.account); if (occupants != null) { foreach (Jid occupant in occupants) { add_occupant(occupant); } } list_box.invalidate_filter(); } private void refilter() { string[]? values = null; string str = search_entry.get_text (); if (str != "") values = str.split(" "); if (filter_values == values) return; filter_values = values; list_box.invalidate_filter(); } public void add_occupant(Jid jid) { rows[jid] = new ListRow(stream_interactor, conversation, jid); list_box.add(rows[jid]); } public void remove_occupant(Jid jid) { list_box.remove(rows[jid]); rows.unset(jid); } private void on_updated_roster_item(Account account, Jid jid, Xmpp.Roster.Item roster_item) { } private void on_show_received(Show show, Jid jid, Account account) { if (conversation != null && conversation.counterpart.equals_bare(jid) && jid.is_full()) { if (show.as == Show.OFFLINE && rows.has_key(jid)) { remove_occupant(jid); } else if (show.as != Show.OFFLINE && !rows.has_key(jid)) { add_occupant(jid); } list_box.invalidate_filter(); } } private void header(ListBoxRow row, ListBoxRow? before_row) { ListRow c1 = row as ListRow; Xmpp.Xep.Muc.Affiliation? a1 = stream_interactor.get_module(MucManager.IDENTITY).get_affiliation(conversation.counterpart, c1.jid, c1.conversation.account); if (a1 == null) return; if (before_row != null) { ListRow c2 = (ListRow) before_row; Xmpp.Xep.Muc.Affiliation? a2 = stream_interactor.get_module(MucManager.IDENTITY).get_affiliation(conversation.counterpart, c2.jid, c2.conversation.account); if (a1 != a2) { row.set_header(generate_header_widget(a1, false)); } else if (row.get_header() != null){ row.set_header(null); } } else { row.set_header(generate_header_widget(a1, true)); } } private Widget generate_header_widget(Xmpp.Xep.Muc.Affiliation affiliation, bool top) { string aff_str; switch (affiliation) { case Xmpp.Xep.Muc.Affiliation.OWNER: aff_str = _("Owner"); break; case Xmpp.Xep.Muc.Affiliation.ADMIN: aff_str = _("Admin"); break; case Xmpp.Xep.Muc.Affiliation.MEMBER: aff_str = _("Member"); break; default: aff_str = _("User"); break; } int count = 0; foreach (ListRow row in rows.values) { Xmpp.Xep.Muc.Affiliation aff = stream_interactor.get_module(MucManager.IDENTITY).get_affiliation(conversation.counterpart, row.jid, conversation.account); if (aff == affiliation) count++; } Label title_label = new Label("") { margin_start=10, xalign=0, visible=true }; title_label.set_markup(@"$(Markup.escape_text(aff_str))"); Label count_label = new Label(@"$count") { xalign=0, margin_end=7, expand=true, visible=true }; count_label.get_style_context().add_class("dim-label"); Grid grid = new Grid() { margin_top=top?5:15, column_spacing=5, hexpand=true, visible=true }; grid.attach(title_label, 0, 0, 1, 1); grid.attach(count_label, 1, 0, 1, 1); grid.attach(new Separator(Orientation.HORIZONTAL) { expand=true, visible=true }, 0, 1, 2, 1); return grid; } private bool filter(ListBoxRow r) { if (r.get_type().is_a(typeof(ListRow))) { ListRow row = r as ListRow; foreach (string filter in filter_values) { return row.name_label.label.down().contains(filter.down()); } } return true; } private int sort(ListBoxRow row1, ListBoxRow row2) { if (row1.get_type().is_a(typeof(ListRow)) && row2.get_type().is_a(typeof(ListRow))) { ListRow c1 = row1 as ListRow; ListRow c2 = row2 as ListRow; int affiliation1 = get_affiliation_ranking(stream_interactor.get_module(MucManager.IDENTITY).get_affiliation(conversation.counterpart, c1.jid, c1.conversation.account) ?? Xmpp.Xep.Muc.Affiliation.NONE); int affiliation2 = get_affiliation_ranking(stream_interactor.get_module(MucManager.IDENTITY).get_affiliation(conversation.counterpart, c2.jid, c2.conversation.account) ?? Xmpp.Xep.Muc.Affiliation.NONE); if (affiliation1 < affiliation2) return -1; else if (affiliation1 > affiliation2) return 1; else return c1.name_label.label.collate(c2.name_label.label); } return 0; } private int get_affiliation_ranking(Xmpp.Xep.Muc.Affiliation affiliation) { switch (affiliation) { case Xmpp.Xep.Muc.Affiliation.OWNER: return 1; case Xmpp.Xep.Muc.Affiliation.ADMIN: return 2; case Xmpp.Xep.Muc.Affiliation.MEMBER: return 3; default: return 4; } } } } dino-0.1.0/main/src/ui/occupant_menu/list_row.vala0000644000000000000000000000142613614354364020652 0ustar rootrootusing Gtk; using Dino.Entities; using Xmpp; namespace Dino.Ui.OccupantMenu { [GtkTemplate (ui = "/im/dino/Dino/occupant_list_item.ui")] public class ListRow : ListBoxRow { [GtkChild] private AvatarImage image; [GtkChild] public Label name_label; public Conversation? conversation; public Jid? jid; public ListRow(StreamInteractor stream_interactor, Conversation conversation, Jid jid) { this.conversation = conversation; this.jid = jid; name_label.label = Util.get_participant_display_name(stream_interactor, conversation, jid); image.set_conversation_participant(stream_interactor, conversation, jid); } public ListRow.label(string c, string text) { name_label.label = text; image.set_text(c); } } } dino-0.1.0/main/src/ui/occupant_menu/view.vala0000644000000000000000000001206713614354364017765 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; using Xmpp; namespace Dino.Ui.OccupantMenu { public class View : Popover { private StreamInteractor stream_interactor; private Conversation conversation; private Stack stack = new Stack() { vhomogeneous=false, visible=true }; private List list; private ListBox invite_list; private Box? jid_menu = null; private Jid? selected_jid; public View(StreamInteractor stream_interactor, Conversation conversation) { this.stream_interactor = stream_interactor; this.conversation = conversation; Box list_box = new Box(Orientation.VERTICAL, 1) { visible=true }; list = new List(stream_interactor, conversation) { visible=true }; list_box.add(list); invite_list = new ListBox() { visible=true }; invite_list.add(new ListRow.label("+", _("Invite")) {visible=true}); list_box.add(invite_list); invite_list.row_activated.connect((row) => { hide(); Gee.List acc_list = new ArrayList(Account.equals_func); acc_list.add(conversation.account); SelectContactDialog add_chat_dialog = new SelectContactDialog(stream_interactor, acc_list); add_chat_dialog.set_transient_for((Window) get_toplevel()); add_chat_dialog.title = _("Invite to Conference"); add_chat_dialog.ok_button.label = _("Invite"); add_chat_dialog.selected.connect((account, jid) => { stream_interactor.get_module(MucManager.IDENTITY).invite(conversation.account, conversation.counterpart, jid); }); add_chat_dialog.present(); }); stack.add_named(list_box, "list"); add(stack); stack.visible_child_name = "list"; list.list_box.row_activated.connect((row) => { ListRow list_row = row as ListRow; show_menu(list_row.jid, list_row.name_label.label); }); hide.connect(reset); } public void reset() { stack.transition_type = StackTransitionType.NONE; stack.visible_child_name = "list"; list.list_box.unselect_all(); invite_list.unselect_all(); } private void show_list() { list.list_box.unselect_all(); stack.transition_type = StackTransitionType.SLIDE_RIGHT; stack.visible_child_name = "list"; } private void show_menu(Jid jid, string name_) { selected_jid = jid; stack.transition_type = StackTransitionType.SLIDE_LEFT; string name = name_; Jid? real_jid = stream_interactor.get_module(MucManager.IDENTITY).get_real_jid(jid, conversation.account); if (real_jid != null) name += @"\n$(real_jid.bare_jid)"; Box header_box = new Box(Orientation.HORIZONTAL, 5) { visible=true }; header_box.add(new Image.from_icon_name("pan-start-symbolic", IconSize.SMALL_TOOLBAR) { visible=true }); header_box.add(new Label(name) { xalign=0, use_markup=true, hexpand=true, visible=true }); Button header_button = new Button() { relief=ReliefStyle.NONE, visible=true }; header_button.add(header_box); Box outer_box = new Box(Orientation.VERTICAL, 5) { margin=10, visible=true }; outer_box.add(header_button); header_button.clicked.connect(show_list); ModelButton private_button = new ModelButton() { active=true, text=_("Start private conversation"), visible=true }; outer_box.add(private_button); private_button.clicked.connect(private_conversation_button_clicked); Jid? own_jid = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account); Xmpp.Xep.Muc.Role? role = stream_interactor.get_module(MucManager.IDENTITY).get_role(own_jid, conversation.account); if (role == Xmpp.Xep.Muc.Role.MODERATOR && stream_interactor.get_module(MucManager.IDENTITY).kick_possible(conversation.account, jid)) { ModelButton kick_button = new ModelButton() { active=true, text=_("Kick"), visible=true }; outer_box.add(kick_button); kick_button.clicked.connect(kick_button_clicked); } if (jid_menu != null) jid_menu.destroy(); stack.add_named(outer_box, "menu"); stack.visible_child_name = "menu"; jid_menu = outer_box; } private void private_conversation_button_clicked() { if (selected_jid == null) return; Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(selected_jid, conversation.account, Conversation.Type.GROUPCHAT_PM); stream_interactor.get_module(ConversationManager.IDENTITY).start_conversation(conversation); Application app = GLib.Application.get_default() as Application; app.controller.select_conversation(conversation); } private void kick_button_clicked() { if (selected_jid == null) return; stream_interactor.get_module(MucManager.IDENTITY).kick(conversation.account, conversation.counterpart, selected_jid.resourcepart); } } } dino-0.1.0/main/src/ui/settings_dialog.vala0000644000000000000000000000230413614354364017323 0ustar rootrootusing Gtk; namespace Dino.Ui { [GtkTemplate (ui = "/im/dino/Dino/settings_dialog.ui")] class SettingsDialog : Dialog { [GtkChild] private CheckButton typing_checkbutton; [GtkChild] private CheckButton marker_checkbutton; [GtkChild] private CheckButton notification_checkbutton; [GtkChild] private CheckButton emoji_checkbutton; Dino.Entities.Settings settings = Dino.Application.get_default().settings; public SettingsDialog() { Object(use_header_bar : Util.use_csd() ? 1 : 0); typing_checkbutton.active = settings.send_typing; marker_checkbutton.active = settings.send_marker; notification_checkbutton.active = settings.notifications; emoji_checkbutton.active = settings.convert_utf8_smileys; typing_checkbutton.toggled.connect(() => { settings.send_typing = typing_checkbutton.active; } ); marker_checkbutton.toggled.connect(() => { settings.send_marker = marker_checkbutton.active; } ); notification_checkbutton.toggled.connect(() => { settings.notifications = notification_checkbutton.active; } ); emoji_checkbutton.toggled.connect(() => { settings.convert_utf8_smileys = emoji_checkbutton.active; }); } } } dino-0.1.0/main/src/ui/unified_window.vala0000644000000000000000000002600513614354364017162 0ustar rootrootusing Gee; using Gdk; using Gtk; using Dino.Entities; namespace Dino.Ui { public class UnifiedWindow : Gtk.Window { public signal void conversation_selected(Conversation conversation); public new string? title { get; set; } public string? subtitle { get; set; } public WelcomePlceholder welcome_placeholder = new WelcomePlceholder() { visible=true }; public NoAccountsPlaceholder accounts_placeholder = new NoAccountsPlaceholder() { visible=true }; public NoConversationsPlaceholder conversations_placeholder = new NoConversationsPlaceholder() { visible=true }; public ChatInput.View chat_input; public ConversationSelector conversation_selector; public ConversationSummary.ConversationView conversation_frame; public ConversationTitlebar conversation_titlebar; public ConversationTitlebarCsd conversation_titlebar_csd; public ConversationListTitlebarCsd conversation_list_titlebar_csd; public HeaderBar placeholder_headerbar = new HeaderBar() { title="Dino", show_close_button=true, visible=true }; public Box box = new Box(Orientation.VERTICAL, 0) { orientation=Orientation.VERTICAL, visible=true }; public Paned headerbar_paned = new Paned(Orientation.HORIZONTAL) { visible=true }; public Paned paned; public Revealer goto_end_revealer; public Button goto_end_button; public Revealer search_revealer; public SearchEntry search_entry; public GlobalSearch search_box; private Stack stack = new Stack() { visible=true }; private Stack left_stack; private Stack right_stack; private StreamInteractor stream_interactor; private Database db; private Config config; public UnifiedWindow(Application application, StreamInteractor stream_interactor, Database db, Config config) { Object(application : application); this.stream_interactor = stream_interactor; this.db = db; this.config = config; restore_window_size(); this.get_style_context().add_class("dino-main"); setup_headerbar(); Gtk.Settings.get_default().notify["gtk-decoration-layout"].connect(set_window_buttons); this.realize.connect(set_window_buttons); setup_unified(); setup_stack(); this.bind_property("title", conversation_titlebar, "title"); this.bind_property("subtitle", conversation_titlebar, "subtitle"); paned.bind_property("position", headerbar_paned, "position", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); stream_interactor.account_added.connect((account) => { check_stack(true); }); stream_interactor.account_removed.connect((account) => { check_stack(); }); stream_interactor.get_module(ConversationManager.IDENTITY).conversation_activated.connect(() => check_stack()); stream_interactor.get_module(ConversationManager.IDENTITY).conversation_deactivated.connect(() => check_stack()); check_stack(); } private void setup_unified() { Builder builder = new Builder.from_resource("/im/dino/Dino/unified_main_content.ui"); paned = (Paned) builder.get_object("paned"); box.add(paned); left_stack = (Stack) builder.get_object("left_stack"); right_stack = (Stack) builder.get_object("right_stack"); chat_input = ((ChatInput.View) builder.get_object("chat_input")).init(stream_interactor); chat_input.key_press_event.connect(forward_key_press_to_chat_input); conversation_frame = ((ConversationSummary.ConversationView) builder.get_object("conversation_frame")).init(stream_interactor); conversation_frame.key_press_event.connect(forward_key_press_to_chat_input); conversation_selector = ((ConversationSelector) builder.get_object("conversation_list")).init(stream_interactor); goto_end_revealer = (Revealer) builder.get_object("goto_end_revealer"); goto_end_button = (Button) builder.get_object("goto_end_button"); search_box = ((GlobalSearch) builder.get_object("search_box")).init(stream_interactor); search_revealer = (Revealer) builder.get_object("search_revealer"); search_entry = (SearchEntry) builder.get_object("search_entry"); Image conversation_list_placeholder_image = (Image) builder.get_object("conversation_list_placeholder_image"); conversation_list_placeholder_image.set_from_pixbuf(new Pixbuf.from_resource("/im/dino/Dino/icons/dino-conversation-list-placeholder-arrow.svg")); } private void setup_headerbar() { if (Util.use_csd()) { conversation_list_titlebar_csd = new ConversationListTitlebarCsd(stream_interactor, this) { visible=true }; headerbar_paned.pack1(conversation_list_titlebar_csd, false, false); conversation_titlebar_csd = new ConversationTitlebarCsd() { visible=true }; conversation_titlebar = conversation_titlebar_csd; headerbar_paned.pack2(conversation_titlebar_csd, true, false); } else { ConversationListTitlebar conversation_list_titlebar = new ConversationListTitlebar(stream_interactor, this) { visible=true }; headerbar_paned.pack1(conversation_list_titlebar, false, false); conversation_titlebar = new ConversationTitlebarNoCsd() { visible=true }; headerbar_paned.pack2(conversation_titlebar, true, false); box.add(headerbar_paned); } headerbar_paned.key_press_event.connect(forward_key_press_to_chat_input); } private void set_window_buttons() { if (!Util.use_csd()) return; Gtk.Settings? gtk_settings = Gtk.Settings.get_default(); if (gtk_settings == null) return; string[] buttons = gtk_settings.gtk_decoration_layout.split(":"); this.conversation_list_titlebar_csd.decoration_layout = buttons[0] + ":"; this.conversation_titlebar_csd.decoration_layout = ((buttons.length == 2) ? ":" + buttons[1] : ""); } private void setup_stack() { stack.add_named(box, "main"); stack.add_named(welcome_placeholder, "welcome_placeholder"); stack.add_named(accounts_placeholder, "accounts_placeholder"); stack.add_named(conversations_placeholder, "conversations_placeholder"); add(stack); } private void check_stack(bool know_exists = false) { ArrayList accounts = stream_interactor.get_accounts(); if (!know_exists && accounts.size == 0) { if (db.get_accounts().size == 0) { stack.set_visible_child_name("welcome_placeholder"); } else { stack.set_visible_child_name("accounts_placeholder"); } if (Util.use_csd()) { set_titlebar(placeholder_headerbar); } } else if (stream_interactor.get_module(ConversationManager.IDENTITY).get_active_conversations().size == 0) { stack.set_visible_child_name("main"); left_stack.set_visible_child_name("placeholder"); right_stack.set_visible_child_name("placeholder"); if (Util.use_csd()) { set_titlebar(headerbar_paned); } } else { left_stack.set_visible_child_name("content"); right_stack.set_visible_child_name("content"); stack.set_visible_child_name("main"); if (Util.use_csd()) { set_titlebar(headerbar_paned); } } } private bool forward_key_press_to_chat_input(EventKey event) { // Don't forward / change focus on Control / Alt if (event.keyval == Gdk.Key.Control_L || event.keyval == Gdk.Key.Control_R || event.keyval == Gdk.Key.Alt_L || event.keyval == Gdk.Key.Alt_R) { return false; } // Don't forward / change focus on Control + ... if ((event.state & ModifierType.CONTROL_MASK) > 0) { return false; } chat_input.text_input.key_press_event(event); chat_input.text_input.grab_focus(); return true; } public void loop_conversations(bool backwards) { conversation_selector.loop_conversations(backwards); } public void restore_window_size() { Gdk.Display? display = Gdk.Display.get_default(); if (display != null) { Gdk.Monitor? monitor = display.get_primary_monitor(); if (monitor == null) { monitor = display.get_monitor_at_point(1, 1); } if (monitor != null && config.window_width <= monitor.geometry.width && config.window_height <= monitor.geometry.height) { set_default_size(config.window_width, config.window_height); } } this.window_position = Gtk.WindowPosition.CENTER; if (config.window_maximize) { maximize(); } this.delete_event.connect(() => { save_window_size(); config.window_maximize = this.is_maximized; return false; }); } public void save_window_size() { if (this.is_maximized) return; Gdk.Display? display = get_display(); Gdk.Window? window = get_window(); if (display != null && window != null) { Gdk.Monitor monitor = display.get_monitor_at_window(window); int width = 0; int height = 0; get_size(out width, out height); // Only store if the values have changed and are reasonable-looking. if (config.window_width != width && width > 0 && width <= monitor.geometry.width) { config.window_width = width; } if (config.window_height != height && height > 0 && height <= monitor.geometry.height) { config.window_height = height; } } } } public class WelcomePlceholder : UnifiedWindowPlaceholder { public WelcomePlceholder() { title_label.label = _("Welcome to Dino!"); label.label = _("Sign in or create an account to get started."); primary_button.label = _("Set up account"); title_label.visible = true; secondary_button.visible = false; } } public class NoAccountsPlaceholder : UnifiedWindowPlaceholder { public NoAccountsPlaceholder() { title_label.label = _("No active accounts"); primary_button.label = _("Manage accounts"); title_label.visible = true; label.visible = false; secondary_button.visible = false; } } public class NoConversationsPlaceholder : UnifiedWindowPlaceholder { public NoConversationsPlaceholder() { title_label.label = _("No active conversations"); primary_button.label = _("Start Conversation"); secondary_button.label = _("Join Channel"); title_label.visible = true; label.visible = false; secondary_button.visible = true; } } [GtkTemplate (ui = "/im/dino/Dino/unified_window_placeholder.ui")] public class UnifiedWindowPlaceholder : Box { [GtkChild] public Label title_label; [GtkChild] public Label label; [GtkChild] public Button primary_button; [GtkChild] public Button secondary_button; } } dino-0.1.0/main/src/ui/unified_window_controller.vala0000644000000000000000000002225713614354364021432 0ustar rootrootusing Gee; using Gdk; using Gtk; using Dino.Entities; namespace Dino.Ui { public class UnifiedWindowController : Object { public new string? conversation_display_name { get; set; } public string? conversation_topic { get; set; } private StreamInteractor stream_interactor; private Conversation? conversation; private Application app; private Database db; private UnifiedWindow window; private SearchMenuEntry search_menu_entry = new SearchMenuEntry(); private ChatInputController chat_input_controller; public UnifiedWindowController(Application application, StreamInteractor stream_interactor, Database db) { this.app = application; this.stream_interactor = stream_interactor; this.db = db; stream_interactor.get_module(MucManager.IDENTITY).room_name_set.connect((account, jid, room_name) => { if (conversation != null && conversation.counterpart.equals_bare(jid) && conversation.account.equals(account)) { update_conversation_display_name(); } }); stream_interactor.get_module(MucManager.IDENTITY).private_room_occupant_updated.connect((account, room, occupant) => { if (conversation != null && conversation.counterpart.equals_bare(room.bare_jid) && conversation.account.equals(account)) { update_conversation_display_name(); } }); stream_interactor.get_module(MucManager.IDENTITY).subject_set.connect((account, jid, subject) => { if (conversation != null && conversation.counterpart.equals_bare(jid) && conversation.account.equals(account)) { update_conversation_topic(subject); } }); stream_interactor.get_module(ConversationManager.IDENTITY).conversation_deactivated.connect(check_unset_conversation); stream_interactor.account_removed.connect(check_unset_conversation); app.plugin_registry.register_contact_titlebar_entry(new MenuEntry(stream_interactor)); app.plugin_registry.register_contact_titlebar_entry(search_menu_entry); app.plugin_registry.register_contact_titlebar_entry(new OccupantsEntry(stream_interactor)); } public void set_window(UnifiedWindow window) { this.window = window; this.chat_input_controller = new ChatInputController(window.chat_input, stream_interactor); this.bind_property("conversation-display-name", window, "title"); this.bind_property("conversation-topic", window, "subtitle"); search_menu_entry.search_button.bind_property("active", window.search_revealer, "reveal_child"); window.goto_end_button.clicked.connect(() => { window.conversation_frame.initialize_for_conversation(conversation); }); window.search_revealer.notify["child-revealed"].connect(() => { if (window.search_revealer.child_revealed) { if (window.conversation_frame.conversation != null && window.search_box.search_entry.text == "") { reset_search_entry(); } window.search_box.search_entry.grab_focus_without_selecting(); window.search_box.search_entry.set_position((int)window.search_box.search_entry.text_length); } }); window.search_box.selected_item.connect((item) => { select_conversation(item.conversation, false, false); window.conversation_frame.initialize_around_message(item.conversation, item); close_search(); }); window.welcome_placeholder.primary_button.clicked.connect(() => { ManageAccounts.AddAccountDialog dialog = new ManageAccounts.AddAccountDialog(stream_interactor, db); dialog.set_transient_for(app.get_active_window()); dialog.present(); }); window.accounts_placeholder.primary_button.clicked.connect(() => { app.activate_action("accounts", null); }); window.conversations_placeholder.primary_button.clicked.connect(() => { app.activate_action("add_chat", null); }); window.conversations_placeholder.secondary_button.clicked.connect(() => { app.activate_action("add_conference", null); }); window.conversation_selector.conversation_selected.connect((conversation) => select_conversation(conversation)); var vadjustment = window.conversation_frame.scrolled.vadjustment; vadjustment.notify["value"].connect(() => { window.goto_end_revealer.reveal_child = vadjustment.value < vadjustment.upper - vadjustment.page_size; }); window.event.connect((event) => { if (event.type == EventType.BUTTON_PRESS) { int dest_x, dest_y; bool ret = window.search_box.translate_coordinates(window, 0, 0, out dest_x, out dest_y); int geometry_x, geometry_y, geometry_width, geometry_height; window.get_window().get_geometry(out geometry_x, out geometry_y, out geometry_width, out geometry_height); if (ret && event.button.x_root - geometry_x < dest_x || event.button.y_root - geometry_y < dest_y) { close_search(); } } else if (event.type == EventType.KEY_RELEASE) { if (event.key.keyval == Gdk.Key.Escape) { close_search(); } } return false; }); window.focus_in_event.connect(() => { stream_interactor.get_module(ChatInteraction.IDENTITY).on_window_focus_in(conversation); window.urgency_hint = false; return false; }); window.focus_out_event.connect(() => { stream_interactor.get_module(ChatInteraction.IDENTITY).on_window_focus_out(conversation); return false; }); window.conversation_selected.connect(conversation => select_conversation(conversation)); } public void select_conversation(Conversation? conversation, bool do_reset_search = true, bool default_initialize_conversation = true) { this.conversation = conversation; update_conversation_display_name(); update_conversation_topic(); foreach(var e in this.app.plugin_registry.conversation_titlebar_entries) { Plugins.ConversationTitlebarWidget widget = e.get_widget(Plugins.WidgetType.GTK); if (widget != null) { widget.set_conversation(conversation); } } stream_interactor.get_module(ChatInteraction.IDENTITY).on_conversation_selected(conversation); conversation.active = true; // only for conversation_selected window.conversation_selector.on_conversation_selected(conversation); // In case selection was not via ConversationSelector if (do_reset_search) { reset_search_entry(); } chat_input_controller.set_conversation(conversation); window.chat_input.initialize_for_conversation(conversation); if (default_initialize_conversation) { window.conversation_frame.initialize_for_conversation(conversation); } } private void check_unset_conversation() { if (stream_interactor.get_module(ConversationManager.IDENTITY).get_active_conversations().size == 0) { unset_conversation(); } } private void unset_conversation() { this.conversation = null; conversation_display_name = null; conversation_topic = null; foreach(var e in this.app.plugin_registry.conversation_titlebar_entries) { Plugins.ConversationTitlebarWidget widget = e.get_widget(Plugins.WidgetType.GTK); if (widget != null) { widget.unset_conversation(); } } } private void update_conversation_display_name() { conversation_display_name = Util.get_conversation_display_name(stream_interactor, conversation); } private void update_conversation_topic(string? subtitle = null) { if (subtitle != null) { conversation_topic = Util.summarize_whitespaces_to_space(subtitle); } else if (conversation.type_ == Conversation.Type.GROUPCHAT) { string? subject = stream_interactor.get_module(MucManager.IDENTITY).get_groupchat_subject(conversation.counterpart, conversation.account); if (subject != null) { conversation_topic = Util.summarize_whitespaces_to_space(subject); } else { conversation_topic = null; } } else { conversation_topic = null; } } private void reset_search_entry() { if (window.conversation_frame.conversation != null) { switch (conversation.type_) { case Conversation.Type.CHAT: case Conversation.Type.GROUPCHAT_PM: window.search_box.search_entry.text = @"with:$(conversation.counterpart) "; break; case Conversation.Type.GROUPCHAT: window.search_box.search_entry.text = @"in:$(conversation.counterpart) "; break; } } } private void close_search() { search_menu_entry.search_button.active = false; window.search_revealer.reveal_child = false; } } } dino-0.1.0/main/src/ui/util/0000755000000000000000000000000013614354364014255 5ustar rootrootdino-0.1.0/main/src/ui/util/accounts_combo_box.vala0000644000000000000000000000307613614354364020776 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; namespace Dino.Ui { class AccountComboBox : ComboBox { public Account? selected { get { TreeIter selected; if (get_active_iter(out selected)) { Value value; list_store.get_value(selected, 1, out value); return value as Account; } return null; } set { TreeIter iter; if (list_store.get_iter_first(out iter)) { int i = 0; do { Value val; list_store.get_value(iter, 1, out val); if ((val as Account).equals(value)) { active = i; break; } i++; } while (list_store.iter_next(ref iter)); } } } private StreamInteractor? stream_interactor; private Gtk.ListStore list_store = new Gtk.ListStore(2, typeof(string), typeof(Account)); public void initialize(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; CellRendererText renderer = new Gtk.CellRendererText(); pack_start(renderer, true); add_attribute(renderer, "text", 0); TreeIter iter; foreach (Account account in stream_interactor.get_accounts()) { list_store.append(out iter); list_store.set(iter, 0, account.bare_jid.to_string(), 1, account); } set_model(list_store); active = 0; } } } dino-0.1.0/main/src/ui/util/config.vala0000644000000000000000000000367613614354364016403 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; namespace Dino.Ui { public class Config : Object { public Database db { get; private set; } public Config(Database db) { this.db = db; window_maximize = col_to_bool_or_default("window_maximized", false); window_width = col_to_int_or_default("window_width", 1200); window_height = col_to_int_or_default("window_height", 700); } private bool window_maximize_; public bool window_maximize { get { return window_maximize_; } set { if (value == window_maximize_) return; db.settings.insert().or("REPLACE").value(db.settings.key, "window_maximized").value(db.settings.value, value.to_string()).perform(); window_maximize_ = value; } } public int window_height_; public int window_height { get { return window_height_; } set { if (value == window_height_) return; db.settings.insert().or("REPLACE").value(db.settings.key, "window_height").value(db.settings.value, value.to_string()).perform(); window_height_ = value; } } public int window_width_; public int window_width { get { return window_width_; } set { if (value == window_width_) return; db.settings.insert().or("REPLACE").value(db.settings.key, "window_width").value(db.settings.value, value.to_string()).perform(); window_width_ = value; } } private bool col_to_bool_or_default(string key, bool def) { string? val = db.settings.select({db.settings.value}).with(db.settings.key, "=", key)[db.settings.value]; return val != null ? bool.parse(val) : def; } private int col_to_int_or_default(string key, int def) { string? val = db.settings.select({db.settings.value}).with(db.settings.key, "=", key)[db.settings.value]; return val != null ? int.parse(val) : def; } } } dino-0.1.0/main/src/ui/util/data_forms.vala0000644000000000000000000000471013614354364017243 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; using Xmpp.Xep; namespace Dino.Ui.Util { public static Widget? get_data_form_field_widget(DataForms.DataForm.Field field) { if (field.type_ == null) return null; switch (field.type_) { case DataForms.DataForm.Type.BOOLEAN: DataForms.DataForm.BooleanField boolean_field = field as DataForms.DataForm.BooleanField; Switch sw = new Switch() { active=boolean_field.value, valign=Align.CENTER, visible=true }; sw.state_set.connect((state) => { boolean_field.value = state; return false; }); return sw; case DataForms.DataForm.Type.JID_MULTI: return null; case DataForms.DataForm.Type.LIST_SINGLE: DataForms.DataForm.ListSingleField list_single_field = field as DataForms.DataForm.ListSingleField; ComboBoxText combobox = new ComboBoxText() { valign=Align.CENTER, visible=true }; for (int i = 0; i < list_single_field.options.size; i++) { DataForms.DataForm.Option option = list_single_field.options[i]; combobox.append(option.value, option.label); if (option.value == list_single_field.value) combobox.active = i; } combobox.changed.connect(() => { list_single_field.value = combobox.get_active_id(); }); return combobox; case DataForms.DataForm.Type.LIST_MULTI: return null; case DataForms.DataForm.Type.TEXT_PRIVATE: DataForms.DataForm.TextPrivateField text_private_field = field as DataForms.DataForm.TextPrivateField; Entry entry = new Entry() { text=text_private_field.value ?? "", valign=Align.CENTER, visible=true, visibility=false }; entry.key_release_event.connect(() => { text_private_field.value = entry.text; return false; }); return entry; case DataForms.DataForm.Type.TEXT_SINGLE: DataForms.DataForm.TextSingleField text_single_field = field as DataForms.DataForm.TextSingleField; Entry entry = new Entry() { text=text_single_field.value ?? "", valign=Align.CENTER, visible=true }; entry.key_release_event.connect(() => { text_single_field.value = entry.text; return false; }); return entry; default: return null; } } } dino-0.1.0/main/src/ui/util/helper.vala0000644000000000000000000005106013614354364016403 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; using Xmpp; namespace Dino.Ui.Util { private static Regex URL_REGEX; private static Map MATCHING_CHARS; private const unichar[] NON_TRAILING_CHARS = {'\'', '"', ',', '.', ';', '!', '?', 'ยป', 'โ€', 'โ€™', '`', '~', 'โ€ฝ', ':', '>', '*', '_'}; private const string[] ALLOWED_SCHEMAS = {"http", "https", "ftp", "ftps", "irc", "ircs", "xmpp", "mailto", "sms", "smsto", "mms", "tel", "geo", "openpgp4fpr", "im", "news", "nntp", "sip", "ssh", "bitcoin", "sftp", "magnet", "vnc"}; private const string[] tango_colors_light = {"FCE94F", "FCAF3E", "E9B96E", "8AE234", "729FCF", "AD7FA8", "EF2929"}; private const string[] tango_colors_medium = {"EDD400", "F57900", "C17D11", "73D216", "3465A4", "75507B", "CC0000"}; private const string[] material_colors_800 = {"D32F2F", "C2185B", "7B1FA2", "512DA8", "303F9F", "1976D2", "0288D1", "0097A7", "00796B", "388E3C", "689F38", "AFB42B", "FFA000", "F57C00", "E64A19", "5D4037"}; private const string[] material_colors_500 = {"F44336", "E91E63", "9C27B0", "673AB7", "3f51B5", "2196F3", "03A9f4", "00BCD4", "009688", "4CAF50", "8BC34a", "CDDC39", "FFC107", "FF9800", "FF5722", "795548"}; private const string[] material_colors_300 = {"E57373", "F06292", "BA68C8", "9575CD", "7986CB", "64B5F6", "4FC3F7", "4DD0E1", "4DB6AC", "81C784", "AED581", "DCE775", "FFD54F", "FFB74D", "FF8A65", "A1887F"}; private const string[] material_colors_200 = {"EF9A9A", "F48FB1", "CE93D8", "B39DDB", "9FA8DA", "90CAF9", "81D4FA", "80DEEA", "80CBC4", "A5D6A7", "C5E1A5", "E6EE9C", "FFE082", "FFCC80", "FFAB91", "BCAAA4"}; public static string get_avatar_hex_color(StreamInteractor stream_interactor, Account account, Jid jid, Conversation? conversation = null) { uint hash = get_relevant_jid(stream_interactor, account, jid, conversation).to_string().hash(); return material_colors_300[hash % material_colors_300.length]; // return tango_colors_light[name.hash() % tango_colors_light.length]; } public static string get_name_hex_color(StreamInteractor stream_interactor, Account account, Jid jid, bool dark_theme = false, Conversation? conversation = null) { uint hash = get_relevant_jid(stream_interactor, account, jid, conversation).to_string().hash(); if (dark_theme) { return material_colors_300[hash % material_colors_300.length]; } else { return material_colors_500[hash % material_colors_500.length]; } // return tango_colors_medium[name.hash() % tango_colors_medium.length]; } private static Jid get_relevant_jid(StreamInteractor stream_interactor, Account account, Jid jid, Conversation? conversation = null) { Conversation conversation_ = conversation ?? stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(jid.bare_jid, account); if (conversation_ != null && conversation_.type_ == Conversation.Type.GROUPCHAT) { Jid? real_jid = stream_interactor.get_module(MucManager.IDENTITY).get_real_jid(jid, account); if (real_jid != null) { return real_jid.bare_jid; } } else { return jid.bare_jid; } return jid; } public static string color_for_show(string show) { switch(show) { case "online": return "#9CCC65"; case "away": return "#FFCA28"; case "chat": return "#66BB6A"; case "xa": return "#EF5350"; case "dnd": return "#EF5350"; default: return "#BDBDBD"; } } public static async AvatarDrawer get_conversation_avatar_drawer(StreamInteractor stream_interactor, Conversation conversation) { return yield get_conversation_participants_avatar_drawer(stream_interactor, conversation, new Jid[0]); } public static async AvatarDrawer get_conversation_participants_avatar_drawer(StreamInteractor stream_interactor, Conversation conversation, Jid[] jids) { AvatarManager avatar_manager = stream_interactor.get_module(AvatarManager.IDENTITY); MucManager muc_manager = stream_interactor.get_module(MucManager.IDENTITY); if (conversation.type_ != Conversation.Type.GROUPCHAT) { Jid jid = jids.length == 1 ? jids[0] : conversation.counterpart; Jid avatar_jid = jid; if (conversation.type_ == Conversation.Type.GROUPCHAT_PM) avatar_jid = muc_manager.get_real_jid(avatar_jid, conversation.account) ?? avatar_jid; return new AvatarDrawer().tile(yield avatar_manager.get_avatar(conversation.account, avatar_jid), jids.length == 1 ? get_participant_display_name(stream_interactor, conversation, jid) : get_conversation_display_name(stream_interactor, conversation), Util.get_avatar_hex_color(stream_interactor, conversation.account, jid, conversation)); } if (jids.length > 0) { AvatarDrawer drawer = new AvatarDrawer(); for (int i = 0; i < (jids.length <= 4 ? jids.length : 3); i++) { Jid avatar_jid = jids[i]; Gdk.Pixbuf? part_avatar = yield avatar_manager.get_avatar(conversation.account, avatar_jid); if (part_avatar == null && avatar_jid.equals_bare(conversation.counterpart) && muc_manager.is_private_room(conversation.account, conversation.counterpart)) { avatar_jid = muc_manager.get_real_jid(avatar_jid, conversation.account) ?? avatar_jid; part_avatar = yield avatar_manager.get_avatar(conversation.account, avatar_jid); } drawer.tile(part_avatar, get_participant_display_name(stream_interactor, conversation, jids[i]), Util.get_avatar_hex_color(stream_interactor, conversation.account, jids[i], conversation)); } if (jids.length > 4) { drawer.plus(); } return drawer; } Gdk.Pixbuf? room_avatar = yield avatar_manager.get_avatar(conversation.account, conversation.counterpart); Gee.List? occupants = muc_manager.get_other_offline_members(conversation.counterpart, conversation.account); if (room_avatar != null || !muc_manager.is_private_room(conversation.account, conversation.counterpart) || occupants == null || occupants.size == 0) { return new AvatarDrawer().tile(room_avatar, "#", Util.get_avatar_hex_color(stream_interactor, conversation.account, conversation.counterpart, conversation)); } AvatarDrawer drawer = new AvatarDrawer(); for (int i = 0; i < (occupants.size <= 4 ? occupants.size : 3); i++) { Jid jid = occupants[i]; Jid avatar_jid = jid; Gdk.Pixbuf? part_avatar = yield avatar_manager.get_avatar(conversation.account, avatar_jid); if (part_avatar == null && avatar_jid.equals_bare(conversation.counterpart) && muc_manager.is_private_room(conversation.account, conversation.counterpart)) { avatar_jid = muc_manager.get_real_jid(avatar_jid, conversation.account) ?? avatar_jid; part_avatar = yield avatar_manager.get_avatar(conversation.account, avatar_jid); } drawer.tile(part_avatar, get_participant_display_name(stream_interactor, conversation, jid), Util.get_avatar_hex_color(stream_interactor, conversation.account, jid, conversation)); } if (occupants.size > 4) { drawer.plus(); } return drawer; } public static string get_conversation_display_name(StreamInteractor stream_interactor, Conversation conversation) { if (conversation.type_ == Conversation.Type.CHAT) { string? display_name = get_real_display_name(stream_interactor, conversation.account, conversation.counterpart); if (display_name != null) return display_name; return conversation.counterpart.to_string(); } if (conversation.type_ == Conversation.Type.GROUPCHAT) { return get_groupchat_display_name(stream_interactor, conversation.account, conversation.counterpart); } if (conversation.type_ == Conversation.Type.GROUPCHAT_PM) { return _("%s from %s").printf(get_occupant_display_name(stream_interactor, conversation.account, conversation.counterpart), get_groupchat_display_name(stream_interactor, conversation.account, conversation.counterpart.bare_jid)); } return conversation.counterpart.to_string(); } public static string get_participant_display_name(StreamInteractor stream_interactor, Conversation conversation, Jid participant, bool me_is_me = false) { if (me_is_me) { if (conversation.account.bare_jid.equals_bare(participant) || (conversation.type_ == Conversation.Type.GROUPCHAT || conversation.type_ == Conversation.Type.GROUPCHAT_PM) && conversation.nickname != null && participant.equals_bare(conversation.counterpart) && conversation.nickname == participant.resourcepart) { return _("Me"); } } if (conversation.type_ == Conversation.Type.CHAT) { return get_real_display_name(stream_interactor, conversation.account, participant, me_is_me) ?? participant.bare_jid.to_string(); } if ((conversation.type_ == Conversation.Type.GROUPCHAT || conversation.type_ == Conversation.Type.GROUPCHAT_PM) && conversation.counterpart.equals_bare(participant)) { return get_occupant_display_name(stream_interactor, conversation.account, participant); } return participant.bare_jid.to_string(); } private static string? get_real_display_name(StreamInteractor stream_interactor, Account account, Jid jid, bool me_is_me = false) { if (jid.equals_bare(account.bare_jid)) { if (me_is_me || account.alias == null || account.alias.length == 0) { return _("Me"); } return account.alias; } Roster.Item roster_item = stream_interactor.get_module(RosterManager.IDENTITY).get_roster_item(account, jid); if (roster_item != null && roster_item.name != null && roster_item.name != "") { return roster_item.name; } return null; } private static string get_groupchat_display_name(StreamInteractor stream_interactor, Account account, Jid jid) { MucManager muc_manager = stream_interactor.get_module(MucManager.IDENTITY); string room_name = muc_manager.get_room_name(account, jid); if (room_name != null && room_name != jid.localpart) { return room_name; } if (muc_manager.is_private_room(account, jid)) { Gee.List? other_occupants = muc_manager.get_other_offline_members(jid, account); if (other_occupants != null && other_occupants.size > 0) { var builder = new StringBuilder (); foreach(Jid occupant in other_occupants) { if (builder.len != 0) { builder.append(", "); } builder.append((get_real_display_name(stream_interactor, account, occupant) ?? occupant.localpart ?? occupant.domainpart).split(" ")[0]); } return builder.str; } } return jid.to_string(); } private static string get_occupant_display_name(StreamInteractor stream_interactor, Account account, Jid jid, bool me_is_me = false, bool muc_real_name = false) { if (muc_real_name) { MucManager muc_manager = stream_interactor.get_module(MucManager.IDENTITY); if (muc_manager.is_private_room(account, jid.bare_jid)) { Jid? real_jid = muc_manager.get_real_jid(jid, account); if (real_jid != null) { string? display_name = get_real_display_name(stream_interactor, account, real_jid, me_is_me); if (display_name != null) return display_name; } } } return jid.resourcepart ?? jid.to_string(); } public static void image_set_from_scaled_pixbuf(Image image, Gdk.Pixbuf pixbuf, int scale = 0, int width = 0, int height = 0) { if (scale == 0) scale = image.scale_factor; Cairo.Surface surface = Gdk.cairo_surface_create_from_pixbuf(pixbuf, scale, image.get_window()); if (height == 0 && width != 0) { height = (int) ((double) width / pixbuf.width * pixbuf.height); } else if (height != 0 && width == 0) { width = (int) ((double) height / pixbuf.height * pixbuf.width); } if (width != 0) { Cairo.Surface surface_new = new Cairo.Surface.similar_image(surface, Cairo.Format.ARGB32, width, height); Cairo.Context context = new Cairo.Context(surface_new); context.scale((double) width * scale / pixbuf.width, (double) height * scale / pixbuf.height); context.set_source_surface(surface, 0, 0); context.get_source().set_filter(Cairo.Filter.BEST); context.paint(); surface = surface_new; } image.set_from_surface(surface); } private const string force_background_css = "%s { background-color: %s; }"; private const string force_color_css = "%s { color: %s; }"; private static void force_css(Gtk.Widget widget, string css) { var p = new Gtk.CssProvider(); try { p.load_from_data(css); widget.get_style_context().add_provider(p, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); } catch (GLib.Error err) { // handle err } } public static void force_background(Gtk.Widget widget, string color, string selector = "*") { force_css(widget, force_background_css.printf(selector, color)); } public static void force_color(Gtk.Widget widget, string color, string selector = "*") { force_css(widget, force_color_css.printf(selector, color)); } public static void force_error_color(Gtk.Widget widget, string selector = "*") { force_color(widget, "@error_color", selector); } public static bool is_dark_theme(Gtk.Widget widget) { Gdk.RGBA bg = widget.get_style_context().get_color(StateFlags.NORMAL); return (bg.red > 0.5 && bg.green > 0.5 && bg.blue > 0.5); } public static bool is_24h_format() { GLib.Settings settings = new GLib.Settings("org.gnome.desktop.interface"); string settings_format = settings.get_string("clock-format"); string p_format = (new DateTime.now_utc()).format("%p"); return settings_format == "24h" || p_format == " "; } public static Regex get_url_regex() { if (URL_REGEX == null) { URL_REGEX = /\b(((http|ftp)s?:\/\/|(ircs?|xmpp|mailto|sms|smsto|mms|tel|geo|openpgp4fpr|im|news|nntp|sip|ssh|bitcoin|sftp|magnet|vnc|urn):)\S+)/; } return URL_REGEX; } public static Map get_matching_chars() { if (MATCHING_CHARS == null) { MATCHING_CHARS = new HashMap(); MATCHING_CHARS[")".get_char(0)] = "(".get_char(0); MATCHING_CHARS["]".get_char(0)] = "[".get_char(0); MATCHING_CHARS["}".get_char(0)] = "{".get_char(0); } return MATCHING_CHARS; } public static string parse_add_markup(string s_, string? highlight_word, bool parse_links, bool parse_text_markup, bool already_escaped_ = false) { string s = s_; bool already_escaped = already_escaped_; if (parse_links) { MatchInfo match_info; get_url_regex().match(s.down(), 0, out match_info); if (match_info.matches()) { int start, end; match_info.fetch_pos(0, out start, out end); string link = s[start:end]; if (GLib.Uri.parse_scheme(link) in ALLOWED_SCHEMAS) { Map matching_chars = get_matching_chars(); unichar close_char; int last_char_index = link.length; while (link.get_prev_char(ref last_char_index, out close_char)) { if (matching_chars.has_key(close_char)) { unichar open_char = matching_chars[close_char]; unichar char; int index = 0; int open = 0, close = 0; while (link.get_next_char(ref index, out char)) { if (char == open_char) { open++; } else if (char == close_char) { close++; } } if (close > open) { // Remove last char from url end -= close_char.to_string().length; link = s[start:end]; } else { break; } } else if (close_char in NON_TRAILING_CHARS) { // Remove last char from url end -= close_char.to_string().length; link = s[start:end]; } else { break; } } return parse_add_markup(s[0:start], highlight_word, parse_links, parse_text_markup, already_escaped) + "" + parse_add_markup(link, highlight_word, false, false, already_escaped) + "" + parse_add_markup(s[end:s.length], highlight_word, parse_links, parse_text_markup, already_escaped); } } } if (!already_escaped) { s = Markup.escape_text(s); already_escaped = true; } if (highlight_word != null) { try { Regex highlight_regex = new Regex("\\b" + Regex.escape_string(highlight_word.down()) + "\\b"); MatchInfo match_info; highlight_regex.match(s.down(), 0, out match_info); if (match_info.matches()) { int start, end; match_info.fetch_pos(0, out start, out end); return parse_add_markup(s[0:start], highlight_word, parse_links, parse_text_markup, already_escaped) + "" + s[start:end] + "" + parse_add_markup(s[end:s.length], highlight_word, parse_links, parse_text_markup, already_escaped); } } catch (RegexError e) { assert_not_reached(); } } if (parse_text_markup) { string[] markup_string = new string[]{"`", "_", "*"}; string[] convenience_tag = new string[]{"tt", "i", "b"}; for (int i = 0; i < markup_string.length; i++) { string markup_esc = Regex.escape_string(markup_string[i]); try { Regex regex = new Regex("(^|\\s)" + markup_esc + "(\\S.*?\\S|\\S)" + markup_esc); MatchInfo match_info; regex.match(s.down(), 0, out match_info); if (match_info.matches()) { int start, end; match_info.fetch_pos(2, out start, out end); return parse_add_markup(s[0:start-1], highlight_word, parse_links, parse_text_markup, already_escaped) + s[start-1:start] + @"<$(convenience_tag[i])>" + s[start:end] + @"" + s[end:end+1] + parse_add_markup(s[end+1:s.length], highlight_word, parse_links, parse_text_markup, already_escaped); } } catch (RegexError e) { assert_not_reached(); } } } return s; } public int get_only_emoji_count(string markup_text) { int emoji_no = 0; int index_ref = 0; unichar curchar = 0, altchar = 0; bool last_was_emoji = false, last_was_modifier_base = false, last_was_keycap = false; while (markup_text.get_next_char(ref index_ref, out curchar)) { if (last_was_emoji && last_was_keycap && curchar == 0x20E3) { // keycap sequence continue; } last_was_keycap = false; if (last_was_emoji && curchar == 0x200D && markup_text.get_next_char(ref index_ref, out curchar)) { // zero width joiner last_was_emoji = false; emoji_no--; } if (last_was_emoji && last_was_modifier_base && ICU.has_binary_property(curchar, ICU.Property.EMOJI_MODIFIER)) { // still an emoji, but no longer a modifier base last_was_modifier_base = false; } else if (ICU.has_binary_property(curchar, ICU.Property.EMOJI_PRESENTATION)) { if (ICU.has_binary_property(curchar, ICU.Property.EMOJI_MODIFIER_BASE)) { last_was_modifier_base = true; } emoji_no++; last_was_emoji = true; } else if (curchar == ' ') { last_was_emoji = false; } else if (markup_text.get_next_char(ref index_ref, out altchar) && altchar == 0xFE0F) { // U+FE0F = VARIATION SELECTOR-16 emoji_no++; last_was_emoji = true; last_was_keycap = (curchar >= 0x30 && curchar <= 0x39) || curchar == 0x23 || curchar == 0x2A; } else { return -1; } } return emoji_no; } public string summarize_whitespaces_to_space(string s) { try { return (/\s+/).replace_literal(s, -1, 0, " "); } catch (RegexError e) { assert_not_reached(); } } public bool use_csd() { return (GLib.Application.get_default() as Application).use_csd(); } } dino-0.1.0/main/src/ui/util/label_hybrid.vala0000644000000000000000000001045213614354364017544 0ustar rootrootusing Gee; using Gtk; namespace Dino.Ui.Util { public class LabelHybrid : Stack { public Label label = new Label("") { visible=true }; protected Button button = new Button() { relief=ReliefStyle.NONE, visible=true }; internal virtual void init(Widget widget) { button.add(label); add_named(button, "label"); add_named(widget, "widget"); button.clicked.connect(() => { show_widget(); }); } public void show_widget() { visible_child_name = "widget"; get_child_by_name("widget").grab_focus(); } public void show_label() { visible_child_name = "label"; } } public class EntryLabelHybrid : LabelHybrid { public string text { get { return entry.text; } set { entry.text = value; set_label_label(value); } } public bool visibility { get { return entry.visibility; } set { entry.visibility = value; } } public float xalign { get { return label.xalign; } set { label.xalign = value; entry.set_alignment(value); } } private Entry? entry_; public Entry entry { get { if (entry_ == null) { entry_ = new Entry() { visible=true }; init(entry_); } return entry_; } set { entry_ = value; } } public EntryLabelHybrid.wrap(Entry e) { init(e); } internal override void init(Widget widget) { Entry? e = widget as Entry; if (e == null) return; entry = e; base.init(entry); update_label(); entry.key_release_event.connect((event) => { if (event.keyval == Gdk.Key.Return) { show_label(); } else { set_label_label(entry.text); } return false; }); entry.focus_out_event.connect(() => { show_label(); return false; }); } private void set_label_label(string value) { if (visibility) { label.label = value; } else { string filler = ""; for (int i = 0; i < value.length; i++) filler += entry.get_invisible_char().to_string(); label.label = filler; } } private void update_label() { text = text; } } public class ComboBoxTextLabelHybrid : LabelHybrid { public int active { get { return combobox.active; } set { combobox.active = value; } } public float xalign { get { return label.xalign; } set { label.xalign = value; } } private ComboBoxText combobox_; public ComboBoxText combobox { get { if (combobox_ == null) { combobox_ = new ComboBoxText() { visible=true }; init(combobox_); } return combobox_; } set { combobox_ = combobox; } } public ComboBoxTextLabelHybrid.wrap(ComboBoxText cb) { combobox_ = cb; init(cb); } public void append(string id, string text) { combobox.append(id, text); } public string get_active_text() { return combobox.get_active_text(); } internal override void init(Widget widget) { ComboBoxText? combobox = widget as ComboBoxText; if (combobox == null) return; base.init(combobox); update_label(); combobox.changed.connect(() => { update_label(); show_label(); }); combobox.focus_out_event.connect(() => { update_label(); show_label(); return false; }); button.clicked.connect(() => { combobox.popup(); }); } private void update_label() { label.label = combobox.get_active_text(); } } public class LabelHybridGroup { private Gee.List hybrids = new ArrayList(); public void add(LabelHybrid hybrid) { hybrids.add(hybrid); hybrid.notify["visible-child-name"].connect(() => { if (hybrid.visible_child_name == "label") return; foreach (LabelHybrid h in hybrids) { if (h != hybrid) { h.set_visible_child_name("label"); } } }); } } } dino-0.1.0/main/src/ui/util/preview_file_chooser_native.vala0000644000000000000000000000451513614354364022677 0ustar rootrootusing Gdk; using Gtk; using Dino.Entities; namespace Dino.Ui { public class PreviewFileChooserNative : Object { private const int PREVIEW_SIZE = 180; private const int PREVIEW_PADDING = 5; private Gtk.FileChooserNative? chooser = null; private Image preview_image = new Image(); public PreviewFileChooserNative(string? title, Gtk.Window? parent, FileChooserAction action, string? accept_label, string? cancel_label) { chooser = new FileChooserNative(title, parent, action, accept_label, cancel_label); chooser.set_preview_widget(this.preview_image); chooser.use_preview_label = false; chooser.preview_widget_active = false; chooser.update_preview.connect(on_update_preview); } public void add_filter(owned Gtk.FileFilter filter) { chooser.add_filter(filter); } public SList get_files() { return chooser.get_files(); } public int run() { return chooser.run(); } public string? get_filename() { return chooser.get_filename(); } private void on_update_preview() { Pixbuf preview_pixbuf = get_preview_pixbuf(); if (preview_pixbuf != null) { int extra_space = PREVIEW_SIZE - preview_pixbuf.width; int smaller_half = extra_space/2; int larger_half = extra_space - smaller_half; preview_image.set_margin_start(PREVIEW_PADDING + smaller_half); preview_image.set_margin_end(PREVIEW_PADDING + larger_half); preview_image.set_from_pixbuf(preview_pixbuf); chooser.preview_widget_active = true; } else { chooser.preview_widget_active = false; } } private Pixbuf? get_preview_pixbuf() { string? filename = chooser.get_preview_filename(); if (filename == null) { return null; } int width = 0; int height = 0; Gdk.PixbufFormat? format = Gdk.Pixbuf.get_file_info(filename, out width, out height); if (format == null) { return null; } try { Gdk.Pixbuf pixbuf = new Gdk.Pixbuf.from_file_at_scale(filename, PREVIEW_SIZE, PREVIEW_SIZE, true); pixbuf = pixbuf.apply_embedded_orientation(); return pixbuf; } catch (Error e) { return null; } } } } dino-0.1.0/main/vapi/0000755000000000000000000000000013614354364013033 5ustar rootrootdino-0.1.0/main/vapi/emojichooser.vapi0000644000000000000000000000030713614354364016402 0ustar rootrootnamespace Dino { [CCode (cheader_filename = "emojichooser.h")] class EmojiChooser : Gtk.Popover { public signal void emoji_picked(string text); public EmojiChooser(); } } dino-0.1.0/main/vapi/icu-uc.vapi0000644000000000000000000000050713614354364015103 0ustar rootrootnamespace ICU { [CCode (cprefix = "UCHAR_", cheader_filename = "unicode/uchar.h")] public enum Property { EMOJI, EMOJI_PRESENTATION, EMOJI_MODIFIER, EMOJI_MODIFIER_BASE, } [CCode (cname = "u_hasBinaryProperty", cheader_filename = "unicode/uchar.h")] public bool has_binary_property(unichar c, Property p); } dino-0.1.0/plugins/0000755000000000000000000000000013614354364012631 5ustar rootrootdino-0.1.0/plugins/CMakeLists.txt0000644000000000000000000000100613614354364015366 0ustar rootrootif(DINO_PLUGIN_ENABLED_http-files) add_subdirectory(http-files) endif(DINO_PLUGIN_ENABLED_http-files) if(DINO_PLUGIN_ENABLED_openpgp) add_subdirectory(gpgme-vala) add_subdirectory(openpgp) endif(DINO_PLUGIN_ENABLED_openpgp) if(DINO_PLUGIN_ENABLED_omemo) add_subdirectory(crypto-vala) add_subdirectory(omemo) add_subdirectory(signal-protocol) endif(DINO_PLUGIN_ENABLED_omemo) if(DINO_PLUGIN_ENABLED_notification-sound) add_subdirectory(notification-sound) endif(DINO_PLUGIN_ENABLED_notification-sound) dino-0.1.0/plugins/crypto-vala/0000755000000000000000000000000013614354364015072 5ustar rootrootdino-0.1.0/plugins/crypto-vala/CMakeLists.txt0000644000000000000000000000121513614354364017631 0ustar rootrootfind_package(GCrypt REQUIRED) find_packages(CRYPTO_VALA_PACKAGES REQUIRED GLib GObject GIO ) vala_precompile(CRYPTO_VALA_C SOURCES "src/cipher.vala" "src/cipher_converter.vala" "src/error.vala" CUSTOM_VAPIS "${CMAKE_CURRENT_SOURCE_DIR}/vapi/gcrypt.vapi" PACKAGES ${CRYPTO_VALA_PACKAGES} GENERATE_VAPI crypto-vala GENERATE_HEADER crypto-vala ) set(CFLAGS ${VALA_CFLAGS} -I${CMAKE_CURRENT_SOURCE_DIR}/src) add_definitions(${CFLAGS}) add_library(crypto-vala ${CRYPTO_VALA_C}) target_link_libraries(crypto-vala ${CRYPTO_VALA_PACKAGES} gcrypt) set_property(TARGET crypto-vala PROPERTY POSITION_INDEPENDENT_CODE ON) dino-0.1.0/plugins/crypto-vala/src/0000755000000000000000000000000013614354364015661 5ustar rootrootdino-0.1.0/plugins/crypto-vala/src/cipher.vala0000644000000000000000000001364713614354364020013 0ustar rootrootnamespace Crypto { public class SymmetricCipher { private GCrypt.Cipher.Cipher cipher; public static bool supports(string algo_name) { GCrypt.Cipher.Algorithm algo; GCrypt.Cipher.Mode mode; GCrypt.Cipher.Flag flags; return parse(algo_name, out algo, out mode, out flags); } private static unowned string mode_to_string(GCrypt.Cipher.Mode mode) { switch (mode) { case GCrypt.Cipher.Mode.ECB: return "ECB"; case GCrypt.Cipher.Mode.CFB: return "CFB"; case GCrypt.Cipher.Mode.CBC: return "CBC"; case GCrypt.Cipher.Mode.STREAM: return "STREAM"; case GCrypt.Cipher.Mode.OFB: return "OFB"; case GCrypt.Cipher.Mode.CTR: return "CTR"; case GCrypt.Cipher.Mode.AESWRAP: return "AESWRAP"; case GCrypt.Cipher.Mode.GCM: return "GCM"; case GCrypt.Cipher.Mode.POLY1305: return "POLY1305"; case GCrypt.Cipher.Mode.OCB: return "OCB"; case GCrypt.Cipher.Mode.CFB8: return "CFB8"; // case GCrypt.Cipher.Mode.XTS: return "XTS"; // Not supported in gcrypt < 1.8 } return "NONE"; } private static GCrypt.Cipher.Mode mode_from_string(string name) { switch (name) { case "ECB": return GCrypt.Cipher.Mode.ECB; case "CFB": return GCrypt.Cipher.Mode.CFB; case "CBC": return GCrypt.Cipher.Mode.CBC; case "STREAM": return GCrypt.Cipher.Mode.STREAM; case "OFB": return GCrypt.Cipher.Mode.OFB; case "CTR": return GCrypt.Cipher.Mode.CTR; case "AESWRAP": return GCrypt.Cipher.Mode.AESWRAP; case "GCM": return GCrypt.Cipher.Mode.GCM; case "POLY1305": return GCrypt.Cipher.Mode.POLY1305; case "OCB": return GCrypt.Cipher.Mode.OCB; case "CFB8": return GCrypt.Cipher.Mode.CFB8; // case "XTS": return GCrypt.Cipher.Mode.XTS; // Not supported in gcrypt < 1.8 } return GCrypt.Cipher.Mode.NONE; } private static string flags_to_string(GCrypt.Cipher.Flag flags) { string? s = null; if ((GCrypt.Cipher.Flag.CBC_MAC & flags) != 0) s = (s == null ? "" : @"$s-") + "MAC"; if ((GCrypt.Cipher.Flag.CBC_CTS & flags) != 0) s = (s == null ? "" : @"$s-") + "CTS"; if ((GCrypt.Cipher.Flag.ENABLE_SYNC & flags) != 0) s = (s == null ? "" : @"$s-") + "SYNC"; if ((GCrypt.Cipher.Flag.SECURE & flags) != 0) s = (s == null ? "" : @"$s-") + "SECURE"; return s ?? "NONE"; } private static GCrypt.Cipher.Flag flag_from_string(string flag_name) { if (flag_name == "SECURE") return GCrypt.Cipher.Flag.SECURE; if (flag_name == "SYNC") return GCrypt.Cipher.Flag.ENABLE_SYNC; if (flag_name == "CTS") return GCrypt.Cipher.Flag.CBC_CTS; if (flag_name == "MAC") return GCrypt.Cipher.Flag.CBC_MAC; return 0; } private static GCrypt.Cipher.Flag flags_from_string(string flag_names) { GCrypt.Cipher.Flag flags = 0; foreach(string flag in flag_names.split("-")) { flags |= flag_from_string(flag); } return flags; } private static bool parse(string algo_name, out GCrypt.Cipher.Algorithm algo, out GCrypt.Cipher.Mode mode, out GCrypt.Cipher.Flag flags) { algo = GCrypt.Cipher.Algorithm.NONE; mode = GCrypt.Cipher.Mode.NONE; flags = 0; string[] algo_parts = algo_name.split("-", 3); algo = GCrypt.Cipher.Algorithm.from_string(algo_parts[0]); if (algo_parts.length >= 2) { mode = mode_from_string(algo_parts[1]); } if (algo_parts.length == 3) { flags |= flags_from_string(algo_parts[2]); } return to_algo_name(algo, mode, flags) == algo_name; } private static string to_algo_name(GCrypt.Cipher.Algorithm algo = GCrypt.Cipher.Algorithm.NONE, GCrypt.Cipher.Mode mode = GCrypt.Cipher.Mode.NONE, GCrypt.Cipher.Flag flags = 0) { if (flags != 0) { return @"$algo-$(mode_to_string(mode))-$(flags_to_string(flags))"; } else if (mode != GCrypt.Cipher.Mode.NONE) { return @"$algo-$(mode_to_string(mode))"; } else { return algo.to_string(); } } public SymmetricCipher(string algo_name) throws Error { GCrypt.Cipher.Algorithm algo; GCrypt.Cipher.Mode mode; GCrypt.Cipher.Flag flags; if (parse(algo_name, out algo, out mode, out flags)) { this.gcrypt(algo, mode, flags); } else { throw new Error.ILLEGAL_ARGUMENTS(@"The algorithm $algo_name is not supported"); } } private SymmetricCipher.gcrypt(GCrypt.Cipher.Algorithm algo, GCrypt.Cipher.Mode mode, GCrypt.Cipher.Flag flags) throws Error { may_throw_gcrypt_error(GCrypt.Cipher.Cipher.open(out this.cipher, algo, mode, flags)); } public void set_key(uint8[] key) throws Error { may_throw_gcrypt_error(cipher.set_key(key)); } public void set_iv(uint8[] iv) throws Error { may_throw_gcrypt_error(cipher.set_iv(iv)); } public void set_counter_vector(uint8[] ctr) throws Error { may_throw_gcrypt_error(cipher.set_counter_vector(ctr)); } public void reset() throws Error { may_throw_gcrypt_error(cipher.reset()); } public uint8[] get_tag(size_t taglen) throws Error { uint8[] tag = new uint8[taglen]; may_throw_gcrypt_error(cipher.get_tag(tag)); return tag; } public void check_tag(uint8[] tag) throws Error { may_throw_gcrypt_error(cipher.check_tag(tag)); } public void encrypt(uint8[] output, uint8[] input) throws Error { may_throw_gcrypt_error(cipher.encrypt(output, input)); } public void decrypt(uint8[] output, uint8[] input) throws Error { may_throw_gcrypt_error(cipher.decrypt(output, input)); } public void sync() throws Error { may_throw_gcrypt_error(cipher.sync()); } } } dino-0.1.0/plugins/crypto-vala/src/cipher_converter.vala0000644000000000000000000001040013614354364022062 0ustar rootrootusing GLib; namespace Crypto { public abstract class SymmetricCipherConverter : Converter, Object { internal SymmetricCipher cipher; internal size_t attached_taglen; public abstract ConverterResult convert(uint8[] inbuf, uint8[] outbuf, ConverterFlags flags, out size_t bytes_read, out size_t bytes_written) throws IOError; public uint8[] get_tag(size_t taglen) throws Error { return cipher.get_tag(taglen); } public void check_tag(uint8[] tag) throws Error { cipher.check_tag(tag); } public void reset() { try { cipher.reset(); } catch (Crypto.Error e) { warning(@"$(e.domain) error while resetting cipher: $(e.message)"); } } } public class SymmetricCipherEncrypter : SymmetricCipherConverter { public SymmetricCipherEncrypter(owned SymmetricCipher cipher, size_t attached_taglen = 0) { this.cipher = (owned) cipher; this.attached_taglen = attached_taglen; } public override ConverterResult convert(uint8[] inbuf, uint8[] outbuf, ConverterFlags flags, out size_t bytes_read, out size_t bytes_written) throws IOError { if (inbuf.length > outbuf.length) { throw new IOError.NO_SPACE("CipherConverter needs at least the size of input as output space"); } if ((flags & ConverterFlags.INPUT_AT_END) != 0 && inbuf.length + attached_taglen > outbuf.length) { throw new IOError.NO_SPACE("CipherConverter needs additional output space to attach tag"); } try { if (inbuf.length > 0) { cipher.encrypt(outbuf, inbuf); } bytes_read = inbuf.length; bytes_written = inbuf.length; if ((flags & ConverterFlags.INPUT_AT_END) != 0) { if (attached_taglen > 0) { Memory.copy((uint8*)outbuf + inbuf.length, get_tag(attached_taglen), attached_taglen); bytes_written = inbuf.length + attached_taglen; } return ConverterResult.FINISHED; } if ((flags & ConverterFlags.FLUSH) != 0) { return ConverterResult.FLUSHED; } return ConverterResult.CONVERTED; } catch (Crypto.Error e) { throw new IOError.FAILED(@"$(e.domain) error while decrypting: $(e.message)"); } } } public class SymmetricCipherDecrypter : SymmetricCipherConverter { public SymmetricCipherDecrypter(owned SymmetricCipher cipher, size_t attached_taglen = 0) { this.cipher = (owned) cipher; this.attached_taglen = attached_taglen; } public override ConverterResult convert(uint8[] inbuf, uint8[] outbuf, ConverterFlags flags, out size_t bytes_read, out size_t bytes_written) throws IOError { if (inbuf.length > outbuf.length + attached_taglen) { throw new IOError.NO_SPACE("CipherConverter needs at least the size of input as output space"); } if ((flags & ConverterFlags.INPUT_AT_END) != 0 && inbuf.length < attached_taglen) { throw new IOError.PARTIAL_INPUT("CipherConverter needs additional input to read tag"); } else if ((flags & ConverterFlags.INPUT_AT_END) == 0 && inbuf.length < attached_taglen + 1) { throw new IOError.PARTIAL_INPUT("CipherConverter needs additional input to make sure to not accidentally read tag"); } try { inbuf.length -= (int) attached_taglen; if (inbuf.length > 0) { cipher.decrypt(outbuf, inbuf); } bytes_read = inbuf.length; bytes_written = inbuf.length; inbuf.length += (int) attached_taglen; if ((flags & ConverterFlags.INPUT_AT_END) != 0) { if (attached_taglen > 0) { check_tag(inbuf[(inbuf.length - attached_taglen):inbuf.length]); bytes_read = inbuf.length; } return ConverterResult.FINISHED; } if ((flags & ConverterFlags.FLUSH) != 0) { return ConverterResult.FLUSHED; } return ConverterResult.CONVERTED; } catch (Crypto.Error e) { throw new IOError.FAILED(@"$(e.domain) error while decrypting: $(e.message)"); } } } } dino-0.1.0/plugins/crypto-vala/src/error.vala0000644000000000000000000000036013614354364017656 0ustar rootrootnamespace Crypto { public errordomain Error { ILLEGAL_ARGUMENTS, GCRYPT } internal void may_throw_gcrypt_error(GCrypt.Error e) throws Error { if (((int)e) != 0) { throw new Crypto.Error.GCRYPT(e.to_string()); } } }dino-0.1.0/plugins/crypto-vala/vapi/0000755000000000000000000000000013614354364016031 5ustar rootrootdino-0.1.0/plugins/crypto-vala/vapi/gcrypt.vapi0000644000000000000000000004650413614354364020233 0ustar rootroot/* gcrypt.vapi * * Copyright: * 2008 Jiqing Qiang * 2008, 2010, 2012-2013 Evan Nemerson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Author: * Jiqing Qiang * Evan Nemerson */ [CCode (cheader_filename = "gcrypt.h", lower_case_cprefix = "gcry_")] namespace GCrypt { [CCode (cname = "gpg_err_source_t", cprefix = "GPG_ERR_SOURCE_")] public enum ErrorSource { UNKNOWN, GCRYPT, GPG, GPGSM, GPGAGENT, PINENTRY, SCD, GPGME, KEYBOX, KSBA, DIRMNGR, GSTI, ANY, USER_1, USER_2, USER_3, USER_4, /* This is one more than the largest allowed entry. */ DIM } [CCode (cname = "gpg_err_code_t", cprefix = "GPG_ERR_")] public enum ErrorCode { NO_ERROR, GENERAL, UNKNOWN_PACKET, UNKNOWN_VERSION, PUBKEY_ALGO, DIGEST_ALGO, BAD_PUBKEY, BAD_SECKEY, BAD_SIGNATURE, NO_PUBKEY, CHECKSUM, BAD_PASSPHRASE, CIPHER_ALGO, KEYRING_OPEN, INV_PACKET, INV_ARMOR, NO_USER_ID, NO_SECKEY, WRONG_SECKEY, BAD_KEY, COMPR_ALGO, NO_PRIME, NO_ENCODING_METHOD, NO_ENCRYPTION_SCHEME, NO_SIGNATURE_SCHEME, INV_ATTR, NO_VALUE, NOT_FOUND, VALUE_NOT_FOUND, SYNTAX, BAD_MPI, INV_PASSPHRASE, SIG_CLASS, RESOURCE_LIMIT, INV_KEYRING, TRUSTDB, BAD_CERT, INV_USER_ID, UNEXPECTED, TIME_CONFLICT, KEYSERVER, WRONG_PUBKEY_ALGO, TRIBUTE_TO_D_A, WEAK_KEY, INV_KEYLEN, INV_ARG, BAD_URI, INV_URI, NETWORK, UNKNOWN_HOST, SELFTEST_FAILED, NOT_ENCRYPTED, NOT_PROCESSED, UNUSABLE_PUBKEY, UNUSABLE_SECKEY, INV_VALUE, BAD_CERT_CHAIN, MISSING_CERT, NO_DATA, BUG, NOT_SUPPORTED, INV_OP, TIMEOUT, INTERNAL, EOF_GCRYPT, INV_OBJ, TOO_SHORT, TOO_LARGE, NO_OBJ, NOT_IMPLEMENTED, CONFLICT, INV_CIPHER_MODE, INV_FLAG, INV_HANDLE, TRUNCATED, INCOMPLETE_LINE, INV_RESPONSE, NO_AGENT, AGENT, INV_DATA, ASSUAN_SERVER_FAULT, ASSUAN, INV_SESSION_KEY, INV_SEXP, UNSUPPORTED_ALGORITHM, NO_PIN_ENTRY, PIN_ENTRY, BAD_PIN, INV_NAME, BAD_DATA, INV_PARAMETER, WRONG_CARD, NO_DIRMNGR, DIRMNGR, CERT_REVOKED, NO_CRL_KNOWN, CRL_TOO_OLD, LINE_TOO_LONG, NOT_TRUSTED, CANCELED, BAD_CA_CERT, CERT_EXPIRED, CERT_TOO_YOUNG, UNSUPPORTED_CERT, UNKNOWN_SEXP, UNSUPPORTED_PROTECTION, CORRUPTED_PROTECTION, AMBIGUOUS_NAME, CARD, CARD_RESET, CARD_REMOVED, INV_CARD, CARD_NOT_PRESENT, NO_PKCS15_APP, NOT_CONFIRMED, CONFIGURATION, NO_POLICY_MATCH, INV_INDEX, INV_ID, NO_SCDAEMON, SCDAEMON, UNSUPPORTED_PROTOCOL, BAD_PIN_METHOD, CARD_NOT_INITIALIZED, UNSUPPORTED_OPERATION, WRONG_KEY_USAGE, NOTHING_FOUND, WRONG_BLOB_TYPE, MISSING_VALUE, HARDWARE, PIN_BLOCKED, USE_CONDITIONS, PIN_NOT_SYNCED, INV_CRL, BAD_BER, INV_BER, ELEMENT_NOT_FOUND, IDENTIFIER_NOT_FOUND, INV_TAG, INV_LENGTH, INV_KEYINFO, UNEXPECTED_TAG, NOT_DER_ENCODED, NO_CMS_OBJ, INV_CMS_OBJ, UNKNOWN_CMS_OBJ, UNSUPPORTED_CMS_OBJ, UNSUPPORTED_ENCODING, UNSUPPORTED_CMS_VERSION, UNKNOWN_ALGORITHM, INV_ENGINE, PUBKEY_NOT_TRUSTED, DECRYPT_FAILED, KEY_EXPIRED, SIG_EXPIRED, ENCODING_PROBLEM, INV_STATE, DUP_VALUE, MISSING_ACTION, MODULE_NOT_FOUND, INV_OID_STRING, INV_TIME, INV_CRL_OBJ, UNSUPPORTED_CRL_VERSION, INV_CERT_OBJ, UNKNOWN_NAME, LOCALE_PROBLEM, NOT_LOCKED, PROTOCOL_VIOLATION, INV_MAC, INV_REQUEST, UNKNOWN_EXTN, UNKNOWN_CRIT_EXTN, LOCKED, UNKNOWN_OPTION, UNKNOWN_COMMAND, BUFFER_TOO_SHORT, SEXP_INV_LEN_SPEC, SEXP_STRING_TOO_LONG, SEXP_UNMATCHED_PAREN, SEXP_NOT_CANONICAL, SEXP_BAD_CHARACTER, SEXP_BAD_QUOTATION, SEXP_ZERO_PREFIX, SEXP_NESTED_DH, SEXP_UNMATCHED_DH, SEXP_UNEXPECTED_PUNC, SEXP_BAD_HEX_CHAR, SEXP_ODD_HEX_NUMBERS, SEXP_BAD_OCT_CHAR, ASS_GENERAL, ASS_ACCEPT_FAILED, ASS_CONNECT_FAILED, ASS_INV_RESPONSE, ASS_INV_VALUE, ASS_INCOMPLETE_LINE, ASS_LINE_TOO_LONG, ASS_NESTED_COMMANDS, ASS_NO_DATA_CB, ASS_NO_INQUIRE_CB, ASS_NOT_A_SERVER, ASS_NOT_A_CLIENT, ASS_SERVER_START, ASS_READ_ERROR, ASS_WRITE_ERROR, ASS_TOO_MUCH_DATA, ASS_UNEXPECTED_CMD, ASS_UNKNOWN_CMD, ASS_SYNTAX, ASS_CANCELED, ASS_NO_INPUT, ASS_NO_OUTPUT, ASS_PARAMETER, ASS_UNKNOWN_INQUIRE, USER_1, USER_2, USER_3, USER_4, USER_5, USER_6, USER_7, USER_8, USER_9, USER_10, USER_11, USER_12, USER_13, USER_14, USER_15, USER_16, MISSING_ERRNO, UNKNOWN_ERRNO, EOF, E2BIG, EACCES, EADDRINUSE, EADDRNOTAVAIL, EADV, EAFNOSUPPORT, EAGAIN, EALREADY, EAUTH, EBACKGROUND, EBADE, EBADF, EBADFD, EBADMSG, EBADR, EBADRPC, EBADRQC, EBADSLT, EBFONT, EBUSY, ECANCELED, ECHILD, ECHRNG, ECOMM, ECONNABORTED, ECONNREFUSED, ECONNRESET, ED, EDEADLK, EDEADLOCK, EDESTADDRREQ, EDIED, EDOM, EDOTDOT, EDQUOT, EEXIST, EFAULT, EFBIG, EFTYPE, EGRATUITOUS, EGREGIOUS, EHOSTDOWN, EHOSTUNREACH, EIDRM, EIEIO, EILSEQ, EINPROGRESS, EINTR, EINVAL, EIO, EISCONN, EISDIR, EISNAM, EL2HLT, EL2NSYNC, EL3HLT, EL3RST, ELIBACC, ELIBBAD, ELIBEXEC, ELIBMAX, ELIBSCN, ELNRNG, ELOOP, EMEDIUMTYPE, EMFILE, EMLINK, EMSGSIZE, EMULTIHOP, ENAMETOOLONG, ENAVAIL, ENEEDAUTH, ENETDOWN, ENETRESET, ENETUNREACH, ENFILE, ENOANO, ENOBUFS, ENOCSI, ENODATA, ENODEV, ENOENT, ENOEXEC, ENOLCK, ENOLINK, ENOMEDIUM, ENOMEM, ENOMSG, ENONET, ENOPKG, ENOPROTOOPT, ENOSPC, ENOSR, ENOSTR, ENOSYS, ENOTBLK, ENOTCONN, ENOTDIR, ENOTEMPTY, ENOTNAM, ENOTSOCK, ENOTSUP, ENOTTY, ENOTUNIQ, ENXIO, EOPNOTSUPP, EOVERFLOW, EPERM, EPFNOSUPPORT, EPIPE, EPROCLIM, EPROCUNAVAIL, EPROGMISMATCH, EPROGUNAVAIL, EPROTO, EPROTONOSUPPORT, EPROTOTYPE, ERANGE, EREMCHG, EREMOTE, EREMOTEIO, ERESTART, EROFS, ERPCMISMATCH, ESHUTDOWN, ESOCKTNOSUPPORT, ESPIPE, ESRCH, ESRMNT, ESTALE, ESTRPIPE, ETIME, ETIMEDOUT, ETOOMANYREFS, ETXTBSY, EUCLEAN, EUNATCH, EUSERS, EWOULDBLOCK, EXDEV, EXFULL, /* This is one more than the largest allowed entry. */ CODE_DIM } [CCode (cname = "gcry_error_t", cprefix = "gpg_err_")] public struct Error : uint { [CCode (cname = "gcry_err_make")] public Error (ErrorSource source, ErrorCode code); [CCode (cname = "gcry_err_make_from_errno")] public Error.from_errno (ErrorSource source, int err); public ErrorCode code (); public ErrorSource source (); [CCode (cname = "gcry_strerror")] public unowned string to_string (); [CCode (cname = "gcry_strsource")] public unowned string source_to_string (); } [CCode (cname = "enum gcry_ctl_cmds", cprefix = "GCRYCTL_")] public enum ControlCommand { SET_KEY, SET_IV, CFB_SYNC, RESET, FINALIZE, GET_KEYLEN, GET_BLKLEN, TEST_ALGO, IS_SECURE, GET_ASNOID, ENABLE_ALGO, DISABLE_ALGO, DUMP_RANDOM_STATS, DUMP_SECMEM_STATS, GET_ALGO_NPKEY, GET_ALGO_NSKEY, GET_ALGO_NSIGN, GET_ALGO_NENCR, SET_VERBOSITY, SET_DEBUG_FLAGS, CLEAR_DEBUG_FLAGS, USE_SECURE_RNDPOOL, DUMP_MEMORY_STATS, INIT_SECMEM, TERM_SECMEM, DISABLE_SECMEM_WARN, SUSPEND_SECMEM_WARN, RESUME_SECMEM_WARN, DROP_PRIVS, ENABLE_M_GUARD, START_DUMP, STOP_DUMP, GET_ALGO_USAGE, IS_ALGO_ENABLED, DISABLE_INTERNAL_LOCKING, DISABLE_SECMEM, INITIALIZATION_FINISHED, INITIALIZATION_FINISHED_P, ANY_INITIALIZATION_P, SET_CBC_CTS, SET_CBC_MAC, SET_CTR, ENABLE_QUICK_RANDOM, SET_RANDOM_SEED_FILE, UPDATE_RANDOM_SEED_FILE, SET_THREAD_CBS, FAST_POLL } public Error control (ControlCommand cmd, ...); [CCode (lower_case_cname = "cipher_")] namespace Cipher { [CCode (cname = "enum gcry_cipher_algos", cprefix = "GCRY_CIPHER_")] public enum Algorithm { NONE, IDEA, 3DES, CAST5, BLOWFISH, SAFER_SK128, DES_SK, AES, AES128, RIJNDAEL, RIJNDAEL128, AES192, RIJNDAEL192, AES256, RIJNDAEL256, TWOFISH, TWOFISH128, ARCFOUR, DES, SERPENT128, SERPENT192, SERPENT256, RFC2268_40, RFC2268_128, SEED, CAMELLIA128, CAMELLIA192, CAMELLIA256, SALSA20, SALSA20R12, GOST28147, CHACHA20; [CCode (cname = "gcry_cipher_algo_info")] public Error info (ControlCommand what, ref uchar[] buffer); [CCode (cname = "gcry_cipher_algo_name")] public unowned string to_string (); [CCode (cname = "gcry_cipher_map_name")] public static Algorithm from_string (string name); [CCode (cname = "gcry_cipher_map_oid")] public static Algorithm from_oid (string oid); } [CCode (cname = "enum gcry_cipher_modes", cprefix = "GCRY_CIPHER_MODE_")] public enum Mode { NONE, /* No mode specified */ ECB, /* Electronic Codebook */ CFB, /* Cipher Feedback */ CBC, /* Cipher Block Chaining */ STREAM, /* Used with stream ciphers */ OFB, /* Output Feedback */ CTR, /* Counter */ AESWRAP, /* AES-WRAP algorithm */ CCM, /* Counter with CBC-MAC */ GCM, /* Galois/Counter Mode */ POLY1305, /* Poly1305 based AEAD mode */ OCB, /* OCB3 mode */ CFB8, /* Cipher Feedback /* Poly1305 based AEAD mode. */ XTS; /* XTS mode */ public unowned string to_string () { switch (this) { case ECB: return "ECB"; case CFB: return "CFB"; case CBC: return "CBC"; case STREAM: return "STREAM"; case OFB: return "OFB"; case CTR: return "CTR"; case AESWRAP: return "AESWRAP"; case GCM: return "GCM"; case POLY1305: return "POLY1305"; case OCB: return "OCB"; case CFB8: return "CFB8"; case XTS: return "XTS"; } return "NONE"; } public static Mode from_string (string name) { switch (name) { case "ECB": return ECB; case "CFB": return CFB; case "CBC": return CBC; case "STREAM": return STREAM; case "OFB": return OFB; case "CTR": return CTR; case "AESWRAP": return AESWRAP; case "GCM": return GCM; case "POLY1305": return POLY1305; case "OCB": return OCB; case "CFB8": return CFB8; case "XTS": return XTS; } return NONE; } } [CCode (cname = "enum gcry_cipher_flags", cprefix = "GCRY_CIPHER_")] public enum Flag { SECURE, /* Allocate in secure memory. */ ENABLE_SYNC, /* Enable CFB sync mode. */ CBC_CTS, /* Enable CBC cipher text stealing (CTS). */ CBC_MAC /* Enable CBC message auth. code (MAC). */ } [CCode (cname = "gcry_cipher_hd_t", lower_case_cprefix = "gcry_cipher_", free_function = "gcry_cipher_close")] [SimpleType] public struct Cipher { public static Error open (out Cipher cipher, Algorithm algo, Mode mode, Flag flags); public void close (); [CCode (cname = "gcry_cipher_ctl")] public Error control (ControlCommand cmd, uchar[] buffer); public Error info (ControlCommand what, ref uchar[] buffer); public Error encrypt (uchar[] out_buffer, uchar[] in_buffer); public Error decrypt (uchar[] out_buffer, uchar[] in_buffer); [CCode (cname = "gcry_cipher_setkey")] public Error set_key (uchar[] key_data); [CCode (cname = "gcry_cipher_setiv")] public Error set_iv (uchar[] iv_data); [CCode (cname = "gcry_cipher_setctr")] public Error set_counter_vector (uchar[] counter_vector); [CCode (cname = "gcry_cipher_gettag")] public Error get_tag(uchar[] out_buffer); [CCode (cname = "gcry_cipher_checktag")] public Error check_tag(uchar[] in_buffer); public Error reset (); public Error sync (); } } [Compact, CCode (cname = "struct gcry_md_handle", cprefix = "gcry_md_", free_function = "gcry_md_close")] public class Hash { [CCode (cname = "enum gcry_md_algos", cprefix = "GCRY_MD_")] public enum Algorithm { NONE, SHA1, RMD160, MD5, MD4, MD2, TIGER, TIGER1, TIGER2, HAVAL, SHA224, SHA256, SHA384, SHA512, SHA3_224, SHA3_256, SHA3_384, SHA3_512, SHAKE128, SHAKE256, CRC32, CRC32_RFC1510, CRC24_RFC2440, WHIRLPOOL, GOSTR3411_94, STRIBOG256, STRIBOG512; [CCode (cname = "gcry_md_get_algo_dlen")] public size_t get_digest_length (); [CCode (cname = "gcry_md_algo_info")] public Error info (ControlCommand what, ref uchar[] buffer); [CCode (cname = "gcry_md_algo_name")] public unowned string to_string (); [CCode (cname = "gcry_md_map_name")] public static Algorithm from_string (string name); [CCode (cname = "gcry_md_test_algo")] public Error is_available (); [CCode (cname = "gcry_md_get_asnoid")] public Error get_oid (uchar[] buffer); } [CCode (cname = "enum gcry_md_flags", cprefix = "GCRY_MD_FLAG_")] public enum Flag { SECURE, HMAC, BUGEMU1 } public static Error open (out Hash hash, Algorithm algo, Flag flag); public void close (); public Error enable (Algorithm algo); [CCode (instance_pos = -1)] public Error copy (out Hash dst); public void reset (); [CCode (cname = "enum gcry_md_ctl")] public Error control (ControlCommand cmd, uchar[] buffer); public void write (uchar[] buffer); [CCode (array_length = false)] public unowned uchar[] read (Algorithm algo); public static void hash_buffer (Algorithm algo, [CCode (array_length = false)] uchar[] digest, uchar[] buffer); public Algorithm get_algo (); public bool is_enabled (Algorithm algo); public bool is_secure (); public Error info (ControlCommand what, uchar[] buffer); [CCode (cname = "gcry_md_setkey")] public Error set_key (uchar[] key_data); public void putc (char c); public void final (); public static Error list (ref Algorithm[] algos); } namespace Random { [CCode (cname = "gcry_random_level_t")] public enum Level { [CCode (cname = "GCRY_WEAK_RANDOM")] WEAK, [CCode (cname = "GCRY_STRONG_RANDOM")] STRONG, [CCode (cname = "GCRY_VERY_STRONG_RANDOM")] VERY_STRONG } [CCode (cname = "gcry_randomize")] public static void randomize (uchar[] buffer, Level level = Level.VERY_STRONG); [CCode (cname = "gcry_fast_random_poll")] public static Error poll (); [CCode (cname = "gcry_random_bytes", array_length = false)] public static uchar[] random_bytes (size_t nbytes, Level level = Level.VERY_STRONG); [CCode (cname = "gcry_random_bytes_secure")] public static uchar[] random_bytes_secure (size_t nbytes, Level level = Level.VERY_STRONG); [CCode (cname = "gcry_create_nonce")] public static void nonce (uchar[] buffer); } [Compact, CCode (cname = "struct gcry_mpi", cprefix = "gcry_mpi_", free_function = "gcry_mpi_release")] public class MPI { [CCode (cname = "enum gcry_mpi_format", cprefix = "GCRYMPI_FMT_")] public enum Format { NONE, STD, PGP, SSH, HEX, USG } [CCode (cname = "enum gcry_mpi_flag", cprefix = "GCRYMPI_FLAG_")] public enum Flag { SECURE, OPAQUE } public MPI (uint nbits); [CCode (cname = "gcry_mpi_snew")] public MPI.secure (uint nbits); public MPI copy (); public void set (MPI u); public void set_ui (ulong u); public void swap (); public int cmp (MPI v); public int cmp_ui (ulong v); public static Error scan (out MPI ret, MPI.Format format, [CCode (array_length = false)] uchar[] buffer, size_t buflen, out size_t nscanned); [CCode (instance_pos = -1)] public Error print (MPI.Format format, [CCode (array_length = false)] uchar[] buffer, size_t buflen, out size_t nwritter); [CCode (instance_pos = -1)] public Error aprint (MPI.Format format, out uchar[] buffer); public void add (MPI u, MPI v); public void add_ui (MPI u, ulong v); public void addm (MPI u, MPI v, MPI m); public void sub (MPI u, MPI v); public void sub_ui (MPI u, MPI v); public void subm (MPI u, MPI v, MPI m); public void mul (MPI u, MPI v); public void mul_ui (MPI u, ulong v); public void mulm (MPI u, MPI v, MPI m); public void mul_2exp (MPI u, ulong cnt); public void div (MPI q, MPI r, MPI dividend, MPI divisor, int round); public void mod (MPI dividend, MPI divisor); public void powm (MPI b, MPI e, MPI m); public int gcd (MPI a, MPI b); public int invm (MPI a, MPI m); public uint get_nbits (); public int test_bit (uint n); public void set_bit (uint n); public void clear_bit (uint n); public void set_highbit (uint n); public void clear_highbit (uint n); public void rshift (MPI a, uint n); public void lshift (MPI a, uint n); public void set_flag (MPI.Flag flag); public void clear_flag (MPI.Flag flag); public int get_flag (MPI.Flag flag); } [Compact, CCode (cname = "struct gcry_sexp", free_function = "gcry_sexp_release")] public class SExp { [CCode (cprefix = "GCRYSEXP_FMT_")] public enum Format { DEFAULT, CANON, BASE64, ADVANCED } public static Error @new (out SExp retsexp, void * buffer, size_t length, int autodetect); public static Error create (out SExp retsexp, void * buffer, size_t length, int autodetect, GLib.DestroyNotify free_function); public static Error sscan (out SExp retsexp, out size_t erroff, char[] buffer); public static Error build (out SExp retsexp, out size_t erroff, string format, ...); public size_t sprint (Format mode, char[] buffer); public static size_t canon_len (uchar[] buffer, out size_t erroff, out int errcode); public SExp find_token (string token, size_t token_length = 0); public int length (); public SExp? nth (int number); public SExp? car (); public SExp? cdr (); public unowned char[] nth_data (int number); public gcry_string nth_string (int number); public MPI nth_mpi (int number, MPI.Format mpifmt); } [CCode (cname = "char", free_function = "gcry_free")] public class gcry_string : string { } [CCode (lower_case_cprefix = "gcry_pk_")] namespace PublicKey { [CCode (cname = "enum gcry_pk_algos")] public enum Algorithm { RSA, ELG_E, DSA, ELG, ECDSA; [CCode (cname = "gcry_pk_algo_name")] public unowned string to_string (); [CCode (cname = "gcry_pk_map_name")] public static Algorithm map_name (string name); } public static Error encrypt (out SExp ciphertext, SExp data, SExp pkey); public static Error decrypt (out SExp plaintext, SExp data, SExp skey); public static Error sign (out SExp signature, SExp data, SExp skey); public static Error verify (SExp signature, SExp data, SExp pkey); public static Error testkey (SExp key); public static Error genkey (out SExp r_key, SExp s_params); public static uint get_nbits (SExp key); } [CCode (lower_case_cprefix = "gcry_kdf_")] namespace KeyDerivation { [CCode (cname = "gcry_kdf_algos", cprefix = "GCRY_KDF_", has_type_id = false)] public enum Algorithm { NONE, SIMPLE_S2K, SALTED_S2K, ITERSALTED_S2K, PBKDF1, PBKDF2, SCRYPT } public GCrypt.Error derive ([CCode (type = "const void*", array_length_type = "size_t")] uint8[] passphrasse, GCrypt.KeyDerivation.Algorithm algo, GCrypt.Hash.Algorithm subalgo, [CCode (type = "const void*", array_length_type = "size_t")] uint8[] salt, ulong iterations, [CCode (type = "void*", array_length_type = "size_t", array_length_pos = 5.5)] uint8[] keybuffer); } } dino-0.1.0/plugins/gpgme-vala/0000755000000000000000000000000013614354364014651 5ustar rootrootdino-0.1.0/plugins/gpgme-vala/CMakeLists.txt0000644000000000000000000000273113614354364017414 0ustar rootrootfind_package(GPGME REQUIRED) find_packages(GPGME_VALA_PACKAGES REQUIRED Gee GLib GObject ) vala_precompile(GPGME_VALA_C SOURCES "src/gpgme_helper.vala" CUSTOM_VAPIS "${CMAKE_CURRENT_SOURCE_DIR}/vapi/gpgme.vapi" "${CMAKE_CURRENT_SOURCE_DIR}/vapi/gpgme_public.vapi" "${CMAKE_CURRENT_SOURCE_DIR}/vapi/gpg-error.vapi" PACKAGES ${GPGME_VALA_PACKAGES} GENERATE_VAPI gpgme-vala GENERATE_HEADER gpgme-vala ) add_custom_command(OUTPUT "${CMAKE_BINARY_DIR}/exports/gpgme_fix.h" COMMAND cp "${CMAKE_CURRENT_SOURCE_DIR}/src/gpgme_fix.h" "${CMAKE_BINARY_DIR}/exports/gpgme_fix.h" DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/gpgme_fix.h" COMMENT Copy header file gpgme_fix.h ) add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/exports/gpgme.vapi COMMAND cat "${CMAKE_BINARY_DIR}/exports/gpgme-vala.vapi" "${CMAKE_CURRENT_SOURCE_DIR}/vapi/gpgme_public.vapi" > "${CMAKE_BINARY_DIR}/exports/gpgme.vapi" DEPENDS ${CMAKE_BINARY_DIR}/exports/gpgme-vala.vapi ${CMAKE_CURRENT_SOURCE_DIR}/vapi/gpgme_public.vapi ) add_custom_target(gpgme-vapi DEPENDS ${CMAKE_BINARY_DIR}/exports/gpgme_fix.h ${CMAKE_BINARY_DIR}/exports/gpgme.vapi ) set(CFLAGS ${VALA_CFLAGS} -I${CMAKE_CURRENT_SOURCE_DIR}/src) add_definitions(${CFLAGS}) add_library(gpgme-vala ${GPGME_VALA_C} src/gpgme_fix.c) add_dependencies(gpgme-vala gpgme-vapi) target_link_libraries(gpgme-vala ${GPGME_VALA_PACKAGES} gpgme) set_property(TARGET gpgme-vala PROPERTY POSITION_INDEPENDENT_CODE ON) dino-0.1.0/plugins/gpgme-vala/src/0000755000000000000000000000000013614354364015440 5ustar rootrootdino-0.1.0/plugins/gpgme-vala/src/gpgme_fix.c0000644000000000000000000000040113614354364017544 0ustar rootroot#include static GRecMutex gpgme_global_mutex = {0}; gpgme_key_t gpgme_key_ref_vapi (gpgme_key_t key) { gpgme_key_ref(key); return key; } gpgme_key_t gpgme_key_unref_vapi (gpgme_key_t key) { gpgme_key_unref(key); return key; }dino-0.1.0/plugins/gpgme-vala/src/gpgme_fix.h0000644000000000000000000000034013614354364017553 0ustar rootroot#ifndef GPGME_FIX #define GPGME_FIX 1 #include #include static GRecMutex gpgme_global_mutex; gpgme_key_t gpgme_key_ref_vapi (gpgme_key_t key); gpgme_key_t gpgme_key_unref_vapi (gpgme_key_t key); #endifdino-0.1.0/plugins/gpgme-vala/src/gpgme_helper.vala0000644000000000000000000001223513614354364020746 0ustar rootrootusing Gee; using GPG; namespace GPGHelper { private static bool initialized = false; public static string encrypt_armor(string plain, Key[] keys, EncryptFlags flags) throws GLib.Error { global_mutex.lock(); try { initialize(); Data plain_data = Data.create_from_memory(plain.data, false); Context context = Context.create(); context.set_armor(true); Data enc_data = context.op_encrypt(keys, flags, plain_data); return get_string_from_data(enc_data); } finally { global_mutex.unlock(); } } public static uint8[] encrypt_file(string uri, Key[] keys, EncryptFlags flags, string file_name) throws GLib.Error { global_mutex.lock(); try { initialize(); Data plain_data = Data.create_from_file(uri); plain_data.set_file_name(file_name); Context context = Context.create(); context.set_armor(true); Data enc_data = context.op_encrypt(keys, flags, plain_data); return get_uint8_from_data(enc_data); } finally { global_mutex.unlock(); } } public static string decrypt(string encr) throws GLib.Error { global_mutex.lock(); try { initialize(); Data enc_data = Data.create_from_memory(encr.data, false); Context context = Context.create(); Data dec_data = context.op_decrypt(enc_data); return get_string_from_data(dec_data); } finally { global_mutex.unlock(); } } public class DecryptedData { public uint8[] data { get; set; } public string filename { get; set; } } public static DecryptedData decrypt_data(uint8[] data) throws GLib.Error { global_mutex.lock(); try { initialize(); Data enc_data = Data.create_from_memory(data, false); Context context = Context.create(); Data dec_data = context.op_decrypt(enc_data); DecryptResult* dec_res = context.op_decrypt_result(); return new DecryptedData() { data=get_uint8_from_data(dec_data), filename=dec_res->file_name}; } finally { global_mutex.unlock(); } } public static string sign(string plain, SigMode mode, Key? key = null) throws GLib.Error { global_mutex.lock(); try { initialize(); Data plain_data = Data.create_from_memory(plain.data, false); Context context = Context.create(); if (key != null) context.signers_add(key); Data signed_data = context.op_sign(plain_data, mode); return get_string_from_data(signed_data); } finally { global_mutex.unlock(); } } public static string? get_sign_key(string signature, string? text) throws GLib.Error { global_mutex.lock(); try { initialize(); Data sig_data = Data.create_from_memory(signature.data, false); Data text_data; if (text != null) { text_data = Data.create_from_memory(text.data, false); } else { text_data = Data.create(); } Context context = Context.create(); context.op_verify(sig_data, text_data); VerifyResult* verify_res = context.op_verify_result(); if (verify_res == null || verify_res.signatures == null) return null; return verify_res.signatures.fpr; } finally { global_mutex.unlock(); } } public static Gee.List get_keylist(string? pattern = null, bool secret_only = false) throws GLib.Error { global_mutex.lock(); try { initialize(); Gee.List keys = new ArrayList(); Context context = Context.create(); context.op_keylist_start(pattern, secret_only ? 1 : 0); try { while (true) { Key key = context.op_keylist_next(); keys.add(key); } } catch (Error e) { if (e.code != GPGError.ErrorCode.EOF) throw e; } return keys; } finally { global_mutex.unlock(); } } public static Key? get_public_key(string sig) throws GLib.Error { return get_key(sig, false); } public static Key? get_private_key(string sig) throws GLib.Error { return get_key(sig, true); } private static Key? get_key(string sig, bool priv) throws GLib.Error { global_mutex.lock(); try { initialize(); Context context = Context.create(); Key key = context.get_key(sig, priv); return key; } finally { global_mutex.unlock(); } } private static string get_string_from_data(Data data) { data.seek(0); uint8[] buf = new uint8[256]; ssize_t? len = null; string res = ""; do { len = data.read(buf); if (len > 0) { string part = (string) buf; part = part.substring(0, (long) len); res += part; } } while (len > 0); return res; } private static uint8[] get_uint8_from_data(Data data) { data.seek(0); uint8[] buf = new uint8[256]; ssize_t? len = null; ByteArray res = new ByteArray(); do { len = data.read(buf); if (len > 0) { res.append(buf[0:len]); } } while (len > 0); return res.data; } private static void initialize() { if (!initialized) { check_version(); initialized = true; } } } dino-0.1.0/plugins/gpgme-vala/vapi/0000755000000000000000000000000013614354364015610 5ustar rootrootdino-0.1.0/plugins/gpgme-vala/vapi/gpg-error.vapi0000644000000000000000000001636213614354364020405 0ustar rootroot/* gcrypt.vapi * * Copyright: * 2008 Jiqing Qiang * 2008, 2010, 2012-2013 Evan Nemerson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Author: * Jiqing Qiang * Evan Nemerson */ [CCode (cheader_filename = "gpg-error.h")] namespace GPGError { [CCode (cname = "gpg_err_source_t", cprefix = "GPG_ERR_SOURCE_")] public enum ErrorSource { UNKNOWN, GCRYPT, GPG, GPGSM, GPGAGENT, PINENTRY, SCD, GPGME, KEYBOX, KSBA, DIRMNGR, GSTI, ANY, USER_1, USER_2, USER_3, USER_4, /* This is one more than the largest allowed entry. */ DIM } [CCode (cname = "gpg_err_code_t", cprefix = "GPG_ERR_")] public enum ErrorCode { NO_ERROR, GENERAL, UNKNOWN_PACKET, UNKNOWN_VERSION, PUBKEY_ALGO, DIGEST_ALGO, BAD_PUBKEY, BAD_SECKEY, BAD_SIGNATURE, NO_PUBKEY, CHECKSUM, BAD_PASSPHRASE, CIPHER_ALGO, KEYRING_OPEN, INV_PACKET, INV_ARMOR, NO_USER_ID, NO_SECKEY, WRONG_SECKEY, BAD_KEY, COMPR_ALGO, NO_PRIME, NO_ENCODING_METHOD, NO_ENCRYPTION_SCHEME, NO_SIGNATURE_SCHEME, INV_ATTR, NO_VALUE, NOT_FOUND, VALUE_NOT_FOUND, SYNTAX, BAD_MPI, INV_PASSPHRASE, SIG_CLASS, RESOURCE_LIMIT, INV_KEYRING, TRUSTDB, BAD_CERT, INV_USER_ID, UNEXPECTED, TIME_CONFLICT, KEYSERVER, WRONG_PUBKEY_ALGO, TRIBUTE_TO_D_A, WEAK_KEY, INV_KEYLEN, INV_ARG, BAD_URI, INV_URI, NETWORK, UNKNOWN_HOST, SELFTEST_FAILED, NOT_ENCRYPTED, NOT_PROCESSED, UNUSABLE_PUBKEY, UNUSABLE_SECKEY, INV_VALUE, BAD_CERT_CHAIN, MISSING_CERT, NO_DATA, BUG, NOT_SUPPORTED, INV_OP, TIMEOUT, INTERNAL, EOF_GCRYPT, INV_OBJ, TOO_SHORT, TOO_LARGE, NO_OBJ, NOT_IMPLEMENTED, CONFLICT, INV_CIPHER_MODE, INV_FLAG, INV_HANDLE, TRUNCATED, INCOMPLETE_LINE, INV_RESPONSE, NO_AGENT, AGENT, INV_DATA, ASSUAN_SERVER_FAULT, ASSUAN, INV_SESSION_KEY, INV_SEXP, UNSUPPORTED_ALGORITHM, NO_PIN_ENTRY, PIN_ENTRY, BAD_PIN, INV_NAME, BAD_DATA, INV_PARAMETER, WRONG_CARD, NO_DIRMNGR, DIRMNGR, CERT_REVOKED, NO_CRL_KNOWN, CRL_TOO_OLD, LINE_TOO_LONG, NOT_TRUSTED, CANCELED, BAD_CA_CERT, CERT_EXPIRED, CERT_TOO_YOUNG, UNSUPPORTED_CERT, UNKNOWN_SEXP, UNSUPPORTED_PROTECTION, CORRUPTED_PROTECTION, AMBIGUOUS_NAME, CARD, CARD_RESET, CARD_REMOVED, INV_CARD, CARD_NOT_PRESENT, NO_PKCS15_APP, NOT_CONFIRMED, CONFIGURATION, NO_POLICY_MATCH, INV_INDEX, INV_ID, NO_SCDAEMON, SCDAEMON, UNSUPPORTED_PROTOCOL, BAD_PIN_METHOD, CARD_NOT_INITIALIZED, UNSUPPORTED_OPERATION, WRONG_KEY_USAGE, NOTHING_FOUND, WRONG_BLOB_TYPE, MISSING_VALUE, HARDWARE, PIN_BLOCKED, USE_CONDITIONS, PIN_NOT_SYNCED, INV_CRL, BAD_BER, INV_BER, ELEMENT_NOT_FOUND, IDENTIFIER_NOT_FOUND, INV_TAG, INV_LENGTH, INV_KEYINFO, UNEXPECTED_TAG, NOT_DER_ENCODED, NO_CMS_OBJ, INV_CMS_OBJ, UNKNOWN_CMS_OBJ, UNSUPPORTED_CMS_OBJ, UNSUPPORTED_ENCODING, UNSUPPORTED_CMS_VERSION, UNKNOWN_ALGORITHM, INV_ENGINE, PUBKEY_NOT_TRUSTED, DECRYPT_FAILED, KEY_EXPIRED, SIG_EXPIRED, ENCODING_PROBLEM, INV_STATE, DUP_VALUE, MISSING_ACTION, MODULE_NOT_FOUND, INV_OID_STRING, INV_TIME, INV_CRL_OBJ, UNSUPPORTED_CRL_VERSION, INV_CERT_OBJ, UNKNOWN_NAME, LOCALE_PROBLEM, NOT_LOCKED, PROTOCOL_VIOLATION, INV_MAC, INV_REQUEST, UNKNOWN_EXTN, UNKNOWN_CRIT_EXTN, LOCKED, UNKNOWN_OPTION, UNKNOWN_COMMAND, BUFFER_TOO_SHORT, SEXP_INV_LEN_SPEC, SEXP_STRING_TOO_LONG, SEXP_UNMATCHED_PAREN, SEXP_NOT_CANONICAL, SEXP_BAD_CHARACTER, SEXP_BAD_QUOTATION, SEXP_ZERO_PREFIX, SEXP_NESTED_DH, SEXP_UNMATCHED_DH, SEXP_UNEXPECTED_PUNC, SEXP_BAD_HEX_CHAR, SEXP_ODD_HEX_NUMBERS, SEXP_BAD_OCT_CHAR, ASS_GENERAL, ASS_ACCEPT_FAILED, ASS_CONNECT_FAILED, ASS_INV_RESPONSE, ASS_INV_VALUE, ASS_INCOMPLETE_LINE, ASS_LINE_TOO_LONG, ASS_NESTED_COMMANDS, ASS_NO_DATA_CB, ASS_NO_INQUIRE_CB, ASS_NOT_A_SERVER, ASS_NOT_A_CLIENT, ASS_SERVER_START, ASS_READ_ERROR, ASS_WRITE_ERROR, ASS_TOO_MUCH_DATA, ASS_UNEXPECTED_CMD, ASS_UNKNOWN_CMD, ASS_SYNTAX, ASS_CANCELED, ASS_NO_INPUT, ASS_NO_OUTPUT, ASS_PARAMETER, ASS_UNKNOWN_INQUIRE, USER_1, USER_2, USER_3, USER_4, USER_5, USER_6, USER_7, USER_8, USER_9, USER_10, USER_11, USER_12, USER_13, USER_14, USER_15, USER_16, MISSING_ERRNO, UNKNOWN_ERRNO, EOF, E2BIG, EACCES, EADDRINUSE, EADDRNOTAVAIL, EADV, EAFNOSUPPORT, EAGAIN, EALREADY, EAUTH, EBACKGROUND, EBADE, EBADF, EBADFD, EBADMSG, EBADR, EBADRPC, EBADRQC, EBADSLT, EBFONT, EBUSY, ECANCELED, ECHILD, ECHRNG, ECOMM, ECONNABORTED, ECONNREFUSED, ECONNRESET, ED, EDEADLK, EDEADLOCK, EDESTADDRREQ, EDIED, EDOM, EDOTDOT, EDQUOT, EEXIST, EFAULT, EFBIG, EFTYPE, EGRATUITOUS, EGREGIOUS, EHOSTDOWN, EHOSTUNREACH, EIDRM, EIEIO, EILSEQ, EINPROGRESS, EINTR, EINVAL, EIO, EISCONN, EISDIR, EISNAM, EL2HLT, EL2NSYNC, EL3HLT, EL3RST, ELIBACC, ELIBBAD, ELIBEXEC, ELIBMAX, ELIBSCN, ELNRNG, ELOOP, EMEDIUMTYPE, EMFILE, EMLINK, EMSGSIZE, EMULTIHOP, ENAMETOOLONG, ENAVAIL, ENEEDAUTH, ENETDOWN, ENETRESET, ENETUNREACH, ENFILE, ENOANO, ENOBUFS, ENOCSI, ENODATA, ENODEV, ENOENT, ENOEXEC, ENOLCK, ENOLINK, ENOMEDIUM, ENOMEM, ENOMSG, ENONET, ENOPKG, ENOPROTOOPT, ENOSPC, ENOSR, ENOSTR, ENOSYS, ENOTBLK, ENOTCONN, ENOTDIR, ENOTEMPTY, ENOTNAM, ENOTSOCK, ENOTSUP, ENOTTY, ENOTUNIQ, ENXIO, EOPNOTSUPP, EOVERFLOW, EPERM, EPFNOSUPPORT, EPIPE, EPROCLIM, EPROCUNAVAIL, EPROGMISMATCH, EPROGUNAVAIL, EPROTO, EPROTONOSUPPORT, EPROTOTYPE, ERANGE, EREMCHG, EREMOTE, EREMOTEIO, ERESTART, EROFS, ERPCMISMATCH, ESHUTDOWN, ESOCKTNOSUPPORT, ESPIPE, ESRCH, ESRMNT, ESTALE, ESTRPIPE, ETIME, ETIMEDOUT, ETOOMANYREFS, ETXTBSY, EUCLEAN, EUNATCH, EUSERS, EWOULDBLOCK, EXDEV, EXFULL, /* This is one more than the largest allowed entry. */ CODE_DIM } [CCode (cname = "gpg_err_code_t", cprefix = "gpg_err_")] public struct Error : uint { [CCode (cname = "gpg_err_make")] public Error (ErrorSource source, ErrorCode code); [CCode (cname = "gpg_err_make_from_errno")] public Error.from_errno (ErrorSource source, int err); public ErrorCode code { [CCode (cname = "gpg_err_code")] get; } public ErrorSource source { [CCode (cname = "gpg_err_source")] get; } [CCode (cname = "gpg_strerror")] public unowned string to_string (); [CCode (cname = "gpg_strsource")] public unowned string source_to_string (); } }dino-0.1.0/plugins/gpgme-vala/vapi/gpgme.deps0000644000000000000000000000001213614354364017555 0ustar rootrootgpg-error dino-0.1.0/plugins/gpgme-vala/vapi/gpgme.vapi0000644000000000000000000003601413614354364017574 0ustar rootroot/* libgpgme.vapi * * Copyright (C) 2009 Sebastian Reichel * * 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. * */ [CCode (lower_case_cprefix = "gpgme_", cheader_filename = "gpgme.h,gpgme_fix.h")] namespace GPG { public static GLib.RecMutex global_mutex; [CCode (cname = "struct _gpgme_engine_info")] public struct EngineInfo { EngineInfo* next; Protocol protocol; string file_name; string version; string req_version; string? home_dir; } [CCode (cname = "struct _gpgme_op_verify_result")] public struct VerifyResult { Signature* signatures; string? file_name; } [CCode (cname = "struct _gpgme_op_sign_result")] public struct SignResult { InvalidKey invalid_signers; Signature* signatures; } [CCode (cname = "struct _gpgme_op_encrypt_result")] public struct EncryptResult { InvalidKey invalid_signers; } [CCode (cname = "struct _gpgme_op_decrypt_result")] public struct DecryptResult { string unsupported_algorithm; bool wrong_key_usage; Recipient recipients; string file_name; } [CCode (cname = "struct _gpgme_recipient")] public struct Recipient { Recipient *next; string keyid; PublicKeyAlgorithm pubkey_algo; GPGError.Error status; } [CCode (cname = "struct _gpgme_invalid_key")] public struct InvalidKey { InvalidKey *next; string fpr; GPGError.Error reason; } [CCode (cname = "struct _gpgme_signature")] public struct Signature { Signature *next; Sigsum summary; string fpr; GPGError.Error status; SigNotation notations; ulong timestamp; ulong exp_timestamp; bool wrong_key_usage; PKAStatus pka_trust; bool chain_model; Validity validity; GPGError.Error validity_reason; PublicKeyAlgorithm pubkey_algo; HashAlgorithm hash_algo; string? pka_adress; } public enum PKAStatus { NOT_AVAILABLE, BAD, OKAY, RFU } [CCode (cname = "gpgme_sigsum_t", cprefix = "GPGME_SIGSUM_")] public enum Sigsum { VALID, GREEN, RED, KEY_REVOKED, KEY_EXPIRED, SIG_EXPIRED, KEY_MISSING, CRL_MISSING, CRL_TOO_OLD, BAD_POLICY, SYS_ERROR } [CCode (cname = "gpgme_data_encoding_t", cprefix = "GPGME_DATA_ENCODING_")] public enum DataEncoding { NONE, BINARY, BASE64, ARMOR, URL, URLESC, URL0 } [CCode (cname = "gpgme_hash_algo_t", cprefix = "GPGME_MD_")] public enum HashAlgorithm { NONE, MD5, SHA1, RMD160, MD2, TIGER, HAVAL, SHA256, SHA384, SHA512, MD4, MD_CRC32, MD_CRC32_RFC1510, MD_CRC24_RFC2440 } [CCode (cname = "gpgme_export_mode_t", cprefix = "GPGME_EXPORT_MODE_")] public enum ExportMode { EXTERN } [CCode (cprefix = "GPGME_AUDITLOG_")] public enum AuditLogFlag { HTML, WITH_HELP } [CCode (cname = "gpgme_status_code_t", cprefix = "GPGME_STATUS_")] public enum StatusCode { EOF, ENTER, LEAVE, ABORT, GOODSIG, BADSIG, ERRSIG, BADARMOR, RSA_OR_IDEA, KEYEXPIRED, KEYREVOKED, TRUST_UNDEFINED, TRUST_NEVER, TRUST_MARGINAL, TRUST_FULLY, TRUST_ULTIMATE, SHM_INFO, SHM_GET, SHM_GET_BOOL, SHM_GET_HIDDEN, NEED_PASSPHRASE, VALIDSIG, SIG_ID, SIG_TO, ENC_TO, NODATA, BAD_PASSPHRASE, NO_PUBKEY, NO_SECKEY, NEED_PASSPHRASE_SYM, DECRYPTION_FAILED, DECRYPTION_OKAY, MISSING_PASSPHRASE, GOOD_PASSPHRASE, GOODMDC, BADMDC, ERRMDC, IMPORTED, IMPORT_OK, IMPORT_PROBLEM, IMPORT_RES, FILE_START, FILE_DONE, FILE_ERROR, BEGIN_DECRYPTION, END_DECRYPTION, BEGIN_ENCRYPTION, END_ENCRYPTION, DELETE_PROBLEM, GET_BOOL, GET_LINE, GET_HIDDEN, GOT_IT, PROGRESS, SIG_CREATED, SESSION_KEY, NOTATION_NAME, NOTATION_DATA, POLICY_URL, BEGIN_STREAM, END_STREAM, KEY_CREATED, USERID_HINT, UNEXPECTED, INV_RECP, NO_RECP, ALREADY_SIGNED, SIGEXPIRED, EXPSIG, EXPKEYSIG, TRUNCATED, ERROR, NEWSIG, REVKEYSIG, SIG_SUBPACKET, NEED_PASSPHRASE_PIN, SC_OP_FAILURE, SC_OP_SUCCESS, CARDCTRL, BACKUP_KEY_CREATED, PKA_TRUST_BAD, PKA_TRUST_GOOD, PLAINTEXT } [Flags] [CCode (cname="unsigned int")] public enum ImportStatusFlags { [CCode (cname = "GPGME_IMPORT_NEW")] NEW, [CCode (cname = "GPGME_IMPORT_UID")] UID, [CCode (cname = "GPGME_IMPORT_SIG")] SIG, [CCode (cname = "GPGME_IMPORT_SUBKEY")] SUBKEY, [CCode (cname = "GPGME_IMPORT_SECRET")] SECRET } [Compact] [CCode (cname = "struct gpgme_context", free_function = "gpgme_release", cprefix = "gpgme_")] public class Context { private static GPGError.Error new(out Context ctx); public static Context create() throws GLib.Error { Context ctx; throw_if_error(@new(out ctx)); return ctx; } public GPGError.Error set_protocol(Protocol p); public Protocol get_protocol(); public void set_armor(bool yes); public bool get_armor(); public void set_textmode(bool yes); public bool get_textmode(); public GPGError.Error set_keylist_mode(KeylistMode mode); public KeylistMode get_keylist_mode(); public void set_include_certs(int nr_of_certs = -256); public int get_include_certs(); public void set_passphrase_cb(passphrase_callback cb, void* hook_value = null); public void get_passphrase_cb(out passphrase_callback cb, out void* hook_value); public GPGError.Error set_locale(int category, string val); [CCode (cname = "gpgme_ctx_get_engine_info")] public EngineInfo* get_engine_info(); [CCode (cname = "gpgme_ctx_set_engine_info")] public GPGError.Error set_engine_info(Protocol proto, string file_name, string home_dir); public void signers_clear(); public GPGError.Error signers_add(Key key); public Key* signers_enum(int n); public void sig_notation_clear(); public GPGError.Error sig_notation_add(string name, string val, SigNotationFlags flags); public SigNotation* sig_notation_get(); [CCode (cname = "gpgme_get_key")] private GPGError.Error get_key_(string fpr, out Key key, bool secret); [CCode (cname = "gpgme_get_key_")] public Key get_key(string fpr, bool secret) throws GLib.Error { Key key; throw_if_error(get_key_(fpr, out key, secret)); return key; } public Context* wait(out GPGError.Error status, bool hang); public SignResult* op_sign_result(); [CCode (cname = "gpgme_op_sign")] public GPGError.Error op_sign_(Data plain, Data sig, SigMode mode); [CCode (cname = "gpgme_op_sign_")] public Data op_sign(Data plain, SigMode mode) throws GLib.Error { Data sig = Data.create(); throw_if_error(op_sign_(plain, sig, mode)); return sig; } public VerifyResult* op_verify_result(); [CCode (cname = "gpgme_op_verify")] public GPGError.Error op_verify_(Data sig, Data signed_text, Data? plaintext); [CCode (cname = "gpgme_op_verify_")] public Data op_verify(Data sig, Data signed_text) throws GLib.Error { Data plaintext = Data.create(); throw_if_error(op_verify_(sig, signed_text, plaintext)); return plaintext; } public EncryptResult* op_encrypt_result(); [CCode (cname = "gpgme_op_encrypt")] public GPGError.Error op_encrypt_([CCode (array_length = false)] Key[] recp, EncryptFlags flags, Data plain, Data cipher); [CCode (cname = "gpgme_op_encrypt_")] public Data op_encrypt(Key[] recp, EncryptFlags flags, Data plain) throws GLib.Error { Data cipher = Data.create(); throw_if_error(op_encrypt_(recp, flags, plain, cipher)); return cipher; } public DecryptResult* op_decrypt_result(); [CCode (cname = "gpgme_op_decrypt")] public GPGError.Error op_decrypt_(Data cipher, Data plain); [CCode (cname = "gpgme_op_decrypt_")] public Data op_decrypt(Data cipher) throws GLib.Error { Data plain = Data.create(); throw_if_error(op_decrypt_(cipher, plain)); return plain; } public GPGError.Error op_export(string? pattern, ExportMode mode, Data keydata); public GPGError.Error op_import(Data keydata); public unowned ImportResult op_import_result(); [CCode (cname = "gpgme_op_keylist_start")] private GPGError.Error op_keylist_start_(string? pattern = null, int secret_only = 0); [CCode (cname = "gpgme_op_keylist_start_")] public void op_keylist_start(string? pattern = null, int secret_only = 0) throws GLib.Error { throw_if_error(op_keylist_start_(pattern, secret_only)); } [CCode (cname = "gpgme_op_keylist_next")] private GPGError.Error op_keylist_next_(out Key key); [CCode (cname = "gpgme_op_keylist_next_")] public Key op_keylist_next() throws GLib.Error { Key key; throw_if_error(op_keylist_next_(out key)); return key; } [CCode (cname = "gpgme_op_keylist_end")] private GPGError.Error op_keylist_end_(); [CCode (cname = "gpgme_op_keylist_end_")] public void op_keylist_end() throws GLib.Error { throw_if_error(op_keylist_end_()); } public KeylistResult op_keylist_result(); } [Compact] [CCode (cname = "struct _gpgme_import_status")] public class ImportStatus { public ImportStatus? next; public string fpr; public GPGError.Error result; public ImportStatusFlags status; } [Compact] [CCode (cname = "struct _gpgme_op_import_result")] public class ImportResult { public int considered; public int no_user_id; public int imported; public int imported_rsa; public int unchanged; public int new_user_ids; public int new_sub_keys; public int new_signatures; public int new_revocations; public int secret_read; public int secret_imported; public int secret_unchanged; public int not_imported; public ImportStatus imports; } [Compact] [CCode (cname = "struct _gpgme_op_keylist_result")] public class KeylistResult { uint truncated; } [Compact] [CCode (cname = "struct gpgme_data", free_function = "gpgme_data_release", cprefix = "gpgme_data_")] public class Data { public static GPGError.Error new(out Data d); public static Data create() throws GLib.Error { Data data; throw_if_error(@new(out data)); return data; } [CCode (cname = "gpgme_data_new_from_mem")] public static GPGError.Error new_from_memory(out Data d, uint8[] buffer, bool copy); public static Data create_from_memory(uint8[] buffer, bool copy) throws GLib.Error { Data data; throw_if_error(new_from_memory(out data, buffer, copy)); return data; } [CCode (cname = "gpgme_data_new_from_file")] public static GPGError.Error new_from_file(out Data d, string filename, int copy = 1); public static Data create_from_file(string filename, int copy = 1) throws GLib.Error { Data data; throw_if_error(new_from_file(out data, filename, copy)); return data; } [CCode (cname = "gpgme_data_release_and_get_mem")] public string release_and_get_mem(out size_t len); public ssize_t read(uint8[] buf); public ssize_t write(uint8[] buf); public long seek(long offset, int whence=0); public GPGError.Error set_file_name(string file_name); public DataEncoding* get_encoding(); public GPGError.Error set_encoding(DataEncoding enc); } [CCode (cname = "gpgme_get_protocol_name")] public unowned string get_protocol_name(Protocol p); [CCode (cname = "gpgme_pubkey_algo_name")] public unowned string get_public_key_algorithm_name(PublicKeyAlgorithm algo); [CCode (cname = "gpgme_hash_algo_name")] public unowned string get_hash_algorithm_name(HashAlgorithm algo); [CCode (cname = "gpgme_passphrase_cb_t", has_target = false)] public delegate GPGError.Error passphrase_callback(void* hook, string uid_hint, string passphrase_info, bool prev_was_bad, int fd); [CCode (cname = "gpgme_engine_check_version")] public GPGError.Error engine_check_version(Protocol proto); [CCode (cname = "gpgme_get_engine_information")] public GPGError.Error get_engine_information(out EngineInfo engine_info); [CCode (cname = "gpgme_strerror_r")] public int strerror_r(GPGError.Error err, uint8[] buf); [CCode (cname = "gpgme_strerror")] public unowned string strerror(GPGError.Error err); private void throw_if_error(GPGError.Error error) throws GLib.Error { if (error.code != GPGError.ErrorCode.NO_ERROR) { throw new GLib.Error(-1, error.code, "%s", error.to_string()); } } } dino-0.1.0/plugins/gpgme-vala/vapi/gpgme_public.vapi0000644000000000000000000000666513614354364021143 0ustar rootroot[CCode (lower_case_cprefix = "gpgme_", cheader_filename = "gpgme.h,gpgme_fix.h")] namespace GPG { [CCode (cname = "gpgme_check_version")] public unowned string check_version(string? required_version = null); [Compact] [CCode (cname = "struct _gpgme_key", ref_function = "gpgme_key_ref_vapi", unref_function = "gpgme_key_unref_vapi", free_function = "gpgme_key_release")] public class Key { public bool revoked; public bool expired; public bool disabled; public bool invalid; public bool can_encrypt; public bool can_sign; public bool can_certify; public bool can_authenticate; public bool is_qualified; public bool secret; public Protocol protocol; public string issuer_serial; public string issuer_name; public string chain_id; public Validity owner_trust; [CCode(array_null_terminated = true)] public SubKey[] subkeys; [CCode(array_null_terminated = true)] public UserID[] uids; public KeylistMode keylist_mode; // public string fpr; // requires gpgme >= 1.7.0 public string fpr { get { return subkeys[0].fpr; } } } [CCode (cname = "struct _gpgme_user_id")] public struct UserID { UserID* next; bool revoked; bool invalid; Validity validity; string uid; string name; string email; string comment; KeySig signatures; } [CCode (cname = "struct _gpgme_key_sig")] public struct KeySig { KeySig* next; bool invoked; bool expired; bool invalid; bool exportable; PublicKeyAlgorithm algo; string keyid; long timestamp; long expires; // GPGError.Error status; string uid; string name; string email; string comment; uint sig_class; SigNotation notations; } [CCode (cname = "struct _gpgme_subkey")] public struct SubKey { SubKey* next; bool revoked; bool expired; bool disabled; bool invalid; bool can_encrypt; bool can_sign; bool can_certify; bool secret; bool can_authenticate; bool is_qualified; bool is_cardkey; PublicKeyAlgorithm algo; uint length; string keyid; string fpr; long timestamp; long expires; string? cardnumber; } [CCode (cname = "struct _gpgme_sig_notation")] public struct SigNotation { SigNotation* next; string? name; string value; int name_len; int value_len; SigNotationFlags flags; bool human_readable; bool critical; } [CCode (cname = "gpgme_sig_notation_flags_t", cprefix = "GPGME_SIG_NOTATION_")] public enum SigNotationFlags { HUMAN_READABLE, CRITICAL } [CCode (cname = "gpgme_sig_mode_t", cprefix = "GPGME_SIG_MODE_")] public enum SigMode { NORMAL, DETACH, CLEAR } [CCode (cname = "gpgme_encrypt_flags_t", cprefix = "GPGME_ENCRYPT_")] public enum EncryptFlags { ALWAYS_TRUST, NO_ENCRYPT_TO } [CCode (cname = "gpgme_pubkey_algo_t", cprefix = "GPGME_PK_")] public enum PublicKeyAlgorithm { RSA, RSA_E, RSA_S, ELG_E, DSA, ELG } [CCode (cname = "gpgme_protocol_t", cprefix = "GPGME_PROTOCOL_")] public enum Protocol { OpenPGP, CMS, GPGCONF, ASSUAN, UNKNOWN } [CCode (cname = "gpgme_keylist_mode_t", cprefix = "GPGME_KEYLIST_MODE_")] public enum KeylistMode { LOCAL, EXTERN, SIGS, SIG_NOTATIONS, EPHEMERAL, VALIDATE } [CCode (cname = "gpgme_validity_t", cprefix = "GPGME_VALIDITY_")] public enum Validity { UNKNOWN, UNDEFINED, NEVER, MARGINAL, FULL, ULTIMATE } }dino-0.1.0/plugins/http-files/0000755000000000000000000000000013614354364014710 5ustar rootrootdino-0.1.0/plugins/http-files/CMakeLists.txt0000644000000000000000000000145213614354364017452 0ustar rootrootfind_packages(HTTP_FILES_PACKAGES REQUIRED Gee GLib GModule GObject GTK3 Soup ) vala_precompile(HTTP_FILES_VALA_C SOURCES src/file_provider.vala src/file_sender.vala src/message_filter.vala src/plugin.vala src/register_plugin.vala CUSTOM_VAPIS ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi ${CMAKE_BINARY_DIR}/exports/dino.vapi ${CMAKE_BINARY_DIR}/exports/qlite.vapi PACKAGES ${HTTP_FILES_PACKAGES} ) add_definitions(${VALA_CFLAGS}) add_library(http-files SHARED ${HTTP_FILES_VALA_C}) target_link_libraries(http-files libdino ${HTTP_FILES_PACKAGES}) set_target_properties(http-files PROPERTIES PREFIX "") set_target_properties(http-files PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins/) install(TARGETS http-files ${PLUGIN_INSTALL}) dino-0.1.0/plugins/http-files/src/0000755000000000000000000000000013614354364015477 5ustar rootrootdino-0.1.0/plugins/http-files/src/file_provider.vala0000644000000000000000000001411113614354364021173 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; using Xmpp; namespace Dino.Plugins.HttpFiles { public class FileProvider : Dino.FileProvider, Object { private StreamInteractor stream_interactor; private Dino.Database dino_db; private static Regex http_url_regex = /^https?:\/\/([^\s#]*)$/; // Spaces are invalid in URLs and we can't use fragments for downloads private static Regex omemo_url_regex = /^aesgcm:\/\/(.*)#(([A-Fa-f0-9]{2}){48}|([A-Fa-f0-9]{2}){44})$/; public FileProvider(StreamInteractor stream_interactor, Dino.Database dino_db) { this.stream_interactor = stream_interactor; this.dino_db = dino_db; stream_interactor.get_module(MessageProcessor.IDENTITY).received_pipeline.connect(new ReceivedMessageListener(this)); } private class ReceivedMessageListener : MessageListener { public string[] after_actions_const = new string[]{ "STORE" }; public override string action_group { get { return ""; } } public override string[] after_actions { get { return after_actions_const; } } private FileProvider outer; private StreamInteractor stream_interactor; public ReceivedMessageListener(FileProvider outer) { this.outer = outer; this.stream_interactor = outer.stream_interactor; } public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) { string? oob_url = Xmpp.Xep.OutOfBandData.get_url_from_message(stanza); bool normal_file = oob_url != null && oob_url == message.body && FileProvider.http_url_regex.match(message.body); bool omemo_file = FileProvider.omemo_url_regex.match(message.body); if (normal_file || omemo_file) { yield outer.on_file_message(message, conversation); } return false; } } private async void on_file_message(Entities.Message message, Conversation conversation) { // Hide message ContentItem? content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_item(conversation, 1, message.id); if (content_item != null) { stream_interactor.get_module(ContentItemStore.IDENTITY).set_item_hide(content_item, true); } var additional_info = message.id.to_string(); var receive_data = new HttpFileReceiveData(); receive_data.url = message.body; var file_meta = new HttpFileMeta(); file_meta.file_name = extract_file_name_from_url(message.body); file_meta.message = message; file_incoming(additional_info, message.from, message.time, message.local_time, conversation, receive_data, file_meta); } public async FileMeta get_meta_info(FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta) throws FileReceiveError { HttpFileReceiveData? http_receive_data = receive_data as HttpFileReceiveData; if (http_receive_data == null) return file_meta; var session = new Soup.Session(); var head_message = new Soup.Message("HEAD", http_receive_data.url); if (head_message != null) { try { yield session.send_async(head_message, null); } catch (Error e) { throw new FileReceiveError.GET_METADATA_FAILED("HEAD request failed"); } string? content_type = null, content_length = null; head_message.response_headers.foreach((name, val) => { if (name == "Content-Type") content_type = val; if (name == "Content-Length") content_length = val; }); file_meta.mime_type = content_type; if (content_length != null) { file_meta.size = int.parse(content_length); } } return file_meta; } public async InputStream download(FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta) throws FileReceiveError { HttpFileReceiveData? http_receive_data = receive_data as HttpFileReceiveData; if (http_receive_data == null) assert(false); try { var session = new Soup.Session(); Soup.Request request = session.request(http_receive_data.url); return yield request.send_async(null); } catch (Error e) { throw new FileReceiveError.DOWNLOAD_FAILED("Downloading file error: %s".printf(e.message)); } } public FileMeta get_file_meta(FileTransfer file_transfer) throws FileReceiveError { Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(file_transfer.counterpart.bare_jid, file_transfer.account); if (conversation == null) throw new FileReceiveError.GET_METADATA_FAILED("No conversation"); Message? message = dino_db.get_message_by_id(int.parse(file_transfer.info)); if (message == null) throw new FileReceiveError.GET_METADATA_FAILED("No message"); var file_meta = new HttpFileMeta(); file_meta.size = file_transfer.size; file_meta.mime_type = file_transfer.mime_type; file_meta.file_name = extract_file_name_from_url(message.body); file_meta.message = message; return file_meta; } public FileReceiveData? get_file_receive_data(FileTransfer file_transfer) { Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(file_transfer.counterpart.bare_jid, file_transfer.account); if (conversation == null) return null; Message? message = dino_db.get_message_by_id(int.parse(file_transfer.info)); if (message == null) return null; var receive_data = new HttpFileReceiveData(); receive_data.url = message.body; return receive_data; } private string extract_file_name_from_url(string url) { string ret = Uri.unescape_string(url.substring(url.last_index_of("/") + 1)); if (ret.contains("#")) { ret = ret.substring(0, ret.last_index_of("#")); } return ret; } public int get_id() { return 0; } } } dino-0.1.0/plugins/http-files/src/file_sender.vala0000644000000000000000000001363613614354364020634 0ustar rootrootusing Dino.Entities; using Xmpp; using Gee; namespace Dino.Plugins.HttpFiles { public class HttpFileSender : FileSender, Object { private StreamInteractor stream_interactor; private Database db; private HashMap max_file_sizes = new HashMap(Account.hash_func, Account.equals_func); public HttpFileSender(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; this.db = db; stream_interactor.stream_negotiated.connect(on_stream_negotiated); stream_interactor.get_module(MessageProcessor.IDENTITY).build_message_stanza.connect(check_add_oob); } public async FileSendData? prepare_send_file(Conversation conversation, FileTransfer file_transfer, FileMeta file_meta) throws FileSendError { HttpFileSendData send_data = new HttpFileSendData(); if (send_data == null) return null; Xmpp.XmppStream? stream = stream_interactor.get_stream(file_transfer.account); if (stream == null) return null; try { var slot_result = yield stream_interactor.module_manager.get_module(file_transfer.account, Xmpp.Xep.HttpFileUpload.Module.IDENTITY).request_slot(stream, file_transfer.server_file_name, file_meta.size, file_meta.mime_type); send_data.url_down = slot_result.url_get; send_data.url_up = slot_result.url_put; send_data.headers = slot_result.headers; } catch (Xep.HttpFileUpload.HttpFileTransferError e) { throw new FileSendError.UPLOAD_FAILED("Http file upload XMPP error: %s".printf(e.message)); } return send_data; } public async void send_file(Conversation conversation, FileTransfer file_transfer, FileSendData file_send_data, FileMeta file_meta) throws FileSendError { HttpFileSendData? send_data = file_send_data as HttpFileSendData; if (send_data == null) return; yield upload(file_transfer, send_data, file_meta); file_transfer.info = send_data.url_down; // store the message content temporarily so the message gets filtered out Entities.Message message = stream_interactor.get_module(MessageProcessor.IDENTITY).create_out_message(send_data.url_down, conversation); message.encryption = send_data.encrypt_message ? conversation.encryption : Encryption.NONE; stream_interactor.get_module(MessageProcessor.IDENTITY).send_message(message, conversation); file_transfer.info = message.id.to_string(); ContentItem? content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_item(conversation, 1, message.id); if (content_item != null) { stream_interactor.get_module(ContentItemStore.IDENTITY).set_item_hide(content_item, true); } } public bool can_send(Conversation conversation, FileTransfer file_transfer) { if (!max_file_sizes.has_key(conversation.account)) return false; return file_transfer.size < max_file_sizes[conversation.account]; } public bool can_encrypt(Conversation conversation, FileTransfer file_transfer) { return false; } public bool is_upload_available(Conversation conversation) { lock (max_file_sizes) { return max_file_sizes.has_key(conversation.account); } } public long get_max_file_size(Account account) { lock (max_file_sizes) { return max_file_sizes[account]; } } private static void transfer_more_bytes(InputStream stream, Soup.MessageBody body) { uint8[] bytes = new uint8[4096]; ssize_t read = stream.read(bytes); if (read == 0) { body.complete(); return; } bytes.length = (int)read; body.append_buffer(new Soup.Buffer.take(bytes)); } private async void upload(FileTransfer file_transfer, HttpFileSendData file_send_data, FileMeta file_meta) throws FileSendError { Xmpp.XmppStream? stream = stream_interactor.get_stream(file_transfer.account); if (stream == null) return; Soup.Message message = new Soup.Message("PUT", file_send_data.url_up); message.request_headers.set_content_type(file_meta.mime_type, null); message.request_headers.set_content_length(file_meta.size); foreach (var entry in file_send_data.headers.entries) { message.request_headers.append(entry.key, entry.value); } message.request_body.set_accumulate(false); message.wrote_headers.connect(() => transfer_more_bytes(file_transfer.input_stream, message.request_body)); message.wrote_chunk.connect(() => transfer_more_bytes(file_transfer.input_stream, message.request_body)); Soup.Session session = new Soup.Session(); try { yield session.send_async(message); if (message.status_code < 200 || message.status_code >= 300) { throw new FileSendError.UPLOAD_FAILED("HTTP status code %s".printf(message.status_code.to_string())); } } catch (Error e) { throw new FileSendError.UPLOAD_FAILED("HTTP upload error: %s".printf(e.message)); } } private void on_stream_negotiated(Account account, XmppStream stream) { stream_interactor.module_manager.get_module(account, Xmpp.Xep.HttpFileUpload.Module.IDENTITY).feature_available.connect((stream, max_file_size) => { lock (max_file_sizes) { max_file_sizes[account] = max_file_size; } upload_available(account); }); } private void check_add_oob(Entities.Message message, Xmpp.MessageStanza message_stanza, Conversation conversation) { if (message.encryption == Encryption.NONE && message_is_file(db, message) && message.body.has_prefix("http")) { Xep.OutOfBandData.add_url_to_message(message_stanza, message_stanza.body); } } public int get_id() { return 0; } public float get_priority() { return 100; } } } dino-0.1.0/plugins/http-files/src/message_filter.vala0000644000000000000000000000101013614354364021325 0ustar rootrootusing Dino.Entities; using Xmpp; using Gee; namespace Dino.Plugins.HttpFiles { public class FileMessageFilter : ContentFilter, Object { public Database db; public FileMessageFilter(Dino.Database db) { this.db = db; } public bool discard(ContentItem content_item) { if (content_item.type_ == MessageItem.TYPE) { MessageItem message_item = content_item as MessageItem; return message_is_file(db, message_item.message); } return false; } } } dino-0.1.0/plugins/http-files/src/plugin.vala0000644000000000000000000000234413614354364017645 0ustar rootrootextern const string GETTEXT_PACKAGE; extern const string LOCALE_INSTALL_DIR; namespace Dino.Plugins.HttpFiles { public class Plugin : RootInterface, Object { public Dino.Application app; public FileProvider file_provider; public FileSender file_sender; public void registered(Dino.Application app) { this.app = app; file_provider = new FileProvider(app.stream_interactor, app.db); file_sender = new HttpFileSender(app.stream_interactor, app.db); app.stream_interactor.get_module(FileManager.IDENTITY).add_provider(file_provider); app.stream_interactor.get_module(FileManager.IDENTITY).add_sender(file_sender); app.stream_interactor.get_module(ContentItemStore.IDENTITY).add_filter(new FileMessageFilter(app.db)); } public void shutdown() { // Nothing to do } } private bool message_is_file(Database db, Entities.Message message) { Qlite.QueryBuilder builder = db.file_transfer.select({db.file_transfer.id}).with(db.file_transfer.info, "=", message.id.to_string()); Qlite.QueryBuilder builder2 = db.file_transfer.select({db.file_transfer.id}).with(db.file_transfer.info, "=", message.body); return builder.count() > 0 || builder2.count() > 0; } } dino-0.1.0/plugins/http-files/src/register_plugin.vala0000644000000000000000000000014213614354364021543 0ustar rootrootpublic Type register_plugin(Module module) { return typeof (Dino.Plugins.HttpFiles.Plugin); } dino-0.1.0/plugins/notification-sound/0000755000000000000000000000000013614354364016445 5ustar rootrootdino-0.1.0/plugins/notification-sound/CMakeLists.txt0000644000000000000000000000146413614354364021212 0ustar rootrootfind_packages(NOTIFICATION_SOUND_PACKAGES REQUIRED Canberra Gee GLib GModule GObject GDKPixbuf2 ) vala_precompile(NOTIFICATION_SOUND_VALA_C SOURCES src/plugin.vala src/register_plugin.vala CUSTOM_VAPIS ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi ${CMAKE_BINARY_DIR}/exports/dino.vapi ${CMAKE_BINARY_DIR}/exports/qlite.vapi PACKAGES ${NOTIFICATION_SOUND_PACKAGES} ) add_definitions(${VALA_CFLAGS}) add_library(notification-sound SHARED ${NOTIFICATION_SOUND_VALA_C}) target_link_libraries(notification-sound libdino ${NOTIFICATION_SOUND_PACKAGES}) set_target_properties(notification-sound PROPERTIES PREFIX "") set_target_properties(notification-sound PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins/) install(TARGETS notification-sound ${PLUGIN_INSTALL}) dino-0.1.0/plugins/notification-sound/src/0000755000000000000000000000000013614354364017234 5ustar rootrootdino-0.1.0/plugins/notification-sound/src/plugin.vala0000644000000000000000000000114313614354364021376 0ustar rootrootnamespace Dino.Plugins.NotificationSound { public class Plugin : RootInterface, Object { public Dino.Application app; private Canberra.Context sound_context; public void registered(Dino.Application app) { this.app = app; Canberra.Context.create(out sound_context); app.stream_interactor.get_module(NotificationEvents.IDENTITY).notify_content_item.connect((item, conversation) => { sound_context.play(0, Canberra.PROP_EVENT_ID, "message-new-instant", Canberra.PROP_EVENT_DESCRIPTION, "New Dino message"); }); } public void shutdown() { } } } dino-0.1.0/plugins/notification-sound/src/register_plugin.vala0000644000000000000000000000015213614354364023301 0ustar rootrootpublic Type register_plugin(Module module) { return typeof (Dino.Plugins.NotificationSound.Plugin); } dino-0.1.0/plugins/omemo/0000755000000000000000000000000013614354364013745 5ustar rootrootdino-0.1.0/plugins/omemo/CMakeLists.txt0000644000000000000000000000470513614354364016513 0ustar rootrootset(GETTEXT_PACKAGE "dino-omemo") find_package(Gettext) include(${GETTEXT_USE_FILE}) gettext_compile(${GETTEXT_PACKAGE} SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/po TARGET_NAME ${GETTEXT_PACKAGE}-translations) find_packages(OMEMO_PACKAGES REQUIRED Gee GLib GModule GObject GTK3 Soup Qrencode ) set(RESOURCE_LIST contact_details_dialog.ui manage_key_dialog.ui ) compile_gresources( OMEMO_GRESOURCES_TARGET OMEMO_GRESOURCES_XML TARGET ${CMAKE_CURRENT_BINARY_DIR}/resources/resources.c TYPE EMBED_C RESOURCES ${RESOURCE_LIST} PREFIX /im/dino/Dino/omemo SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/data ) vala_precompile(OMEMO_VALA_C SOURCES src/plugin.vala src/register_plugin.vala src/trust_level.vala src/file_transfer/file_decryptor.vala src/file_transfer/file_encryptor.vala src/jingle/jingle_helper.vala src/jingle/jet_omemo.vala src/logic/database.vala src/logic/encrypt_state.vala src/logic/manager.vala src/logic/pre_key_store.vala src/logic/session_store.vala src/logic/signed_pre_key_store.vala src/logic/trust_manager.vala src/protocol/bundle.vala src/protocol/message_flag.vala src/protocol/stream_module.vala src/ui/account_settings_entry.vala src/ui/account_settings_widget.vala src/ui/contact_details_provider.vala src/ui/contact_details_dialog.vala src/ui/device_notification_populator.vala src/ui/own_notifications.vala src/ui/encryption_list_entry.vala src/ui/manage_key_dialog.vala src/ui/util.vala CUSTOM_VAPIS ${CMAKE_BINARY_DIR}/exports/crypto-vala.vapi ${CMAKE_BINARY_DIR}/exports/signal-protocol.vapi ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi ${CMAKE_BINARY_DIR}/exports/qlite.vapi ${CMAKE_BINARY_DIR}/exports/dino.vapi PACKAGES ${OMEMO_PACKAGES} GRESOURCES ${OMEMO_GRESOURCES_XML} OPTIONS --vapidir=${CMAKE_CURRENT_SOURCE_DIR}/vapi ) add_definitions(${VALA_CFLAGS} -DGETTEXT_PACKAGE=\"${GETTEXT_PACKAGE}\" -DLOCALE_INSTALL_DIR=\"${LOCALE_INSTALL_DIR}\" -DG_LOG_DOMAIN="OMEMO") add_library(omemo SHARED ${OMEMO_VALA_C} ${OMEMO_GRESOURCES_TARGET}) add_dependencies(omemo ${GETTEXT_PACKAGE}-translations) target_link_libraries(omemo libdino signal-protocol-vala crypto-vala ${OMEMO_PACKAGES}) set_target_properties(omemo PROPERTIES PREFIX "") set_target_properties(omemo PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins/) install(TARGETS omemo ${PLUGIN_INSTALL}) dino-0.1.0/plugins/omemo/data/0000755000000000000000000000000013614354364014656 5ustar rootrootdino-0.1.0/plugins/omemo/data/contact_details_dialog.ui0000644000000000000000000004614113614354364021702 0ustar rootroot False show_qrcode_button left True True 10 True dino-0.1.0/plugins/omemo/data/manage_key_dialog.ui0000644000000000000000000002303413614354364020636 0ustar rootroot dino-0.1.0/plugins/omemo/po/0000755000000000000000000000000013614354364014363 5ustar rootrootdino-0.1.0/plugins/omemo/po/LINGUAS0000644000000000000000000000012113614354364015402 0ustar rootrootar ca de en es eu fi fr gl hu it ja lb nb nl nl_BE pl pt_BR ro ru sv zh_CN zh_TW dino-0.1.0/plugins/omemo/po/ar.po0000644000000000000000000002277513614354364015342 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2020-01-12 13:21+0000\n" "Language-Team: Arabic \n" "Language: ar\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " "&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n" "X-Generator: Weblate 3.10.1\n" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "ุงู„ุฅุฏุงุฑุฉ" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "ู„ุฏู‰ ู‡ุฐุง ุงู„ู…ูุฑุงุณูู„ ุฃุฌู‡ุฒุฉ ุฌุฏูŠุฏุฉ" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "ู‚ุฑุงุฑ ุงู„ุซู‚ุฉ ุจู€ OMEMO ู…ุทู„ูˆุจ" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "ู‡ู„ ู‚ู…ุช ุจุฅุถุงูุฉ ุฌู‡ุงุฒ ุฌุฏูŠุฏ ุนู„ู‰ ุญุณุงุจ %sุŸ" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "ุฅุฏุงุฑุฉ ุงู„ู…ูุชุงุญ" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contacts device." msgstr "ู‚ุงุฑู† ุงู„ุจุตู…ุฉ ุญุฑูุง ุจุญุฑู ุŒ ู…ุน ุงู„ุชูŠ ุชุธู‡ุฑ ุนู„ู‰ ุฌู‡ุงุฒ ู…ูุฑุงุณูู„ูƒ." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Not matching" msgstr "ุบูŠุฑ ู…ุทุงุจู‚ุฉ" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Matching" msgstr "ู…ุทุงุจู‚ุฉ" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "ุฅู„ุบุงุก" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "ุชุฃูƒูŠุฏ" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "ุงู„ุชุญู‚ู‚ ู…ูู† ุงู„ู…ูุชุงุญ" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "highlighted accordingly in the chat window." msgstr "" "ุจู…ุฌุฑุฏ ุชุฃูƒูŠุฏ ุฐู„ูƒ ุŒ ุณูŠุชู… ุชู…ูŠูŠุฒ ุฃูŠุฉ ุฑุณุงุฆู„ ู…ุณุชู‚ุจู„ูŠุฉ ู…ุฑุณู„ุฉ ู…ูู† %s ุจุงุณุชุฎุฏุงู… ู‡ุฐุง " "ุงู„ู…ูุชุงุญ ูˆูู‚ุง ู„ุฐู„ูƒ ููŠ ู†ุงูุฐุฉ ุงู„ุฏุฑุฏุดุฉ." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "ุงู„ุจุตู…ุงุช ุบูŠุฑ ู…ุชุทุงุจู‚ุฉ" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "ูŠุฑุฌู‰ ุงู„ุชุญู‚ู‚ ุจุงู„ู…ู‚ุงุฑู†ุฉ ู…ุน ุงู„ุจุตู…ุฉ ุงู„ุตุญูŠุญุฉ.ุฅู† ุนุฏู… ุชุทุงุจู‚ ุงู„ุจุตู…ุงุช ูŠุนู†ูŠ ุฃู†ู‘ู‡ ู…ูู† " "ุงู„ู…ุญุชู…ู„ ุฃู†ู‘ ุญุณุงุจ %s ู‚ุฏ ุชุนุฑุถ ู„ุงุฎุชุฑุงู‚ ูˆุฃู†ู‡ ู…ูู† ุงู„ู…ุณุชุญุณู† ุงู„ุชููƒูŠุฑ ููŠ ุฑูุถ ู‡ุฐุง " "ุงู„ู…ูุชุงุญ." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "ุงู„ุชุญู‚ู‚ ู…ูู† ู…ูุชุงุญ ุงู„ุจุตู…ุฉ" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "ู‚ุงุฑู† ุจุตู…ุฉ ู‡ุฐุง ุงู„ู…ูุชุงุญ ู…ุน ุงู„ุจุตู…ุฉ ุงู„ู…ุนุฑูˆุถุฉ ุนู„ู‰ ุฌู‡ุงุฒ ู…ูุฑุงุณูู„ูƒ." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "ุฑูุถ ุงู„ู…ูุชุงุญ" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Stop accepting this key during communication with its associated contact." msgstr "ุชูˆู‚ู ุนู† ู‚ุจูˆู„ ู‡ุฐุง ุงู„ู…ูุชุงุญ ุฃุซู†ุงุก ุงู„ุงุชุตุงู„ ู…ุน ุงู„ู…ูุฑุงุณูู„ ุงู„ู…ุฑุชุจุท ุจู‡." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "ุงู‚ุจู„ ุงู„ู…ูุชุงุญ" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Start accepting this key during communication with its associated contact" msgstr "ุงุจุฏุฃ ููŠ ู‚ุจูˆู„ ู‡ุฐุง ุงู„ู…ูุชุงุญ ุฃุซู†ุงุก ุงู„ุงุชุตุงู„ ู…ุน ุงู„ู…ูุฑุงุณูู„ ุงู„ู…ุฑุชุจุท ุจู‡" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "ุงู„ู…ูุชุงุญ ุงู„ุขู† %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "ู…ู‚ุจูˆู„" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send messages." msgstr "ูˆู‡ุฐุง ูŠุนู†ูŠ ุฃู†ู‡ ูŠู…ูƒู† ุฃู† ูŠุณุชุฎุฏู…ู‡ %s ู„ุชู„ู‚ูŠ ูˆุฅุฑุณุงู„ ุงู„ุฑุณุงุฆู„." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "ุชู… ุงู„ุชุญู‚ู‚ ู…ู†ู‡" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "ุจุงู„ุฅุถุงูุฉ ุฅู„ู‰ ุฃู†ู‡ ู‚ุฏ ุชู… ุงู„ุชุญู‚ู‚ ู…ู† ุชุทุงุจู‚ ุงู„ู…ูุชุงุญ ุนู„ู‰ ุฌู‡ุงุฒ ุงู„ู…ูุฑุงุณูู„." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "ู…ุฑููˆุถ" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to receive messages, and any messages " "sent by it will be ignored." msgstr "" "ู‡ุฐุง ูŠุนู†ูŠ ุฃู†ู‡ ู„ุง ูŠู…ูƒู† ุงุณุชุฎุฏุงู…ู‡ ู…ูู† ุทุฑู %s ู„ุงุณุชู‚ุจุงู„ ุงู„ุฑุณุงุฆู„ ูˆ ุณูŠุชู… ุชุฌุงู‡ู„ ุฃูŠุฉ " "ุฑุณุงุฆู„ ุจูุนูุซุช ุจู‡." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "ignored and none of your messages will be readable using this key." msgstr "" "ุจู…ุฌุฑุฏ ุงู„ุชุฃูƒูŠุฏ ุŒ ุณูŠุชู… ุชุฌุงู‡ู„ ุฃูŠ ุฑุณุงุฆู„ ู…ุณุชู‚ุจู„ูŠุฉ ูŠู‚ูˆู… ุจุฅุฑุณุงู„ู‡ุง %s ุจุงุณุชุฎุฏุงู… ู‡ุฐุง " "ุงู„ู…ูุชุงุญ ูˆูŠุชุตุจุญ ูƒุงูุฉ ุฑุณุงุฆู„ูƒ ุบูŠุฑ ู‚ุงุจู„ุฉ ู„ู„ู‚ุฑุงุกุฉ ุจุงุณุชุฎุฏุงู… ู‡ุฐุง ุงู„ู…ูุชุงุญ." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "Once confirmed this key will be usable by %s to receive and send messages." msgstr "" "ุจู…ุฌุฑุฏ ุชุฃูƒูŠุฏ ู‡ุฐุง ุงู„ู…ูุชุงุญ ุณูˆู ูŠูƒูˆู† ู‚ุงุจู„ุง ู„ู„ุงุณุชุฎุฏุงู… ู…ู† ู‚ูุจู„ %s ู„ุชู„ู‚ูŠ ูˆุฅุฑุณุงู„ " "ุงู„ุฑุณุงุฆู„." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "ุงู„ุนูˆุฏุฉ" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "ุฅุฏุงุฑุฉ ู…ูุงุชูŠุญ ุฃูˆู…ูŠู…ูˆ" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "ุชู‚ุจู‘ู„ ุงู„ู…ูุงุชูŠุญ ุงู„ุฌุฏูŠุฏุฉ ุชู„ู‚ุงุฆูŠุงู‹" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "" "When this contact adds new encryption keys to their account, automatically " "accept them." msgstr "" "ุนู†ุฏ ู‚ูŠุงู… ุฌู‡ุฉ ุงู„ุงุชุตุงู„ ู‡ุฐู‡ ุจุงุถุงูุฉ ู…ูุงุชูŠุญ ุชุดููŠุฑ ุฌุฏูŠุฏุฉ ุฅู„ู‰ ุญุณุงุจู‡ุง ุŒ ู‚ู… ุจู‚ุจูˆู„ู‡ุง " "ุชู„ู‚ุงุฆูŠุง." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "ู…ูุชุงุญูƒ ุงู„ุฎุงุต" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "ุงู„ู…ูุงุชูŠุญ ุงู„ุฌุฏูŠุฏุฉ" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "ุงู„ู…ูุงุชูŠุญ ุงู„ู…ู‚ุชุฑูู†ุฉ" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "ุงู„ู…ูุงุชูŠุญ ุบูŠุฑ ุงู„ู…ูู†ุดู‘ูŽุทุฉ" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "When you add new encryption keys to your account, automatically accept them." msgstr "ุนู†ุฏ ุฅุถุงูุฉ ู…ูุงุชูŠุญ ุชุดููŠุฑ ุฌุฏูŠุฏุฉ ุฅู„ู‰ ุญุณุงุจูƒ ุŒ ุงู‚ุจู„ู‡ุง ุชู„ู‚ุงุฆูŠุง." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "ุชู… ู‚ุจูˆู„ู‡" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "ุชู… ุฑูุถู‡" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "ุชู… ุงู„ุชุญู‚ู‚ ู…ู†ู‡" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "ุบูŠุฑ ู…ุณุชุฎุฏูŽู…" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "ุจุตู…ุชูƒ ุงู„ุฎุงุตุฉ" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connect" msgstr "ุณูŠุชู… ุชูˆู„ูŠุฏู‡ุง ุนู†ุฏ ุฃูˆู„ ุงุชุตุงู„" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "ุงู„ุชุดููŠุฑ" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d ู„ุง ูŠูˆุฌุฏ ุฌู‡ุงุฒ OMEMO" msgstr[1] "%d ุฌู‡ุงุฒ OMEMO" msgstr[2] "ุฌู‡ุงุฒูŠู† ู„ู€ OMEMO" msgstr[3] "%d ุฃุฌู‡ุฒุฉ OMEMO" msgstr[4] "%d ุฌู‡ุงุฒ OMEMO" msgstr[5] "%d ุฃุฌู‡ุฒุฉ OMEMO" #~ msgid "Unknown device (0x%.8x)" #~ msgstr "ุฌู‡ุงุฒ ู…ุฌู‡ูˆู„ (0x%.8x)" #~ msgid "Other devices" #~ msgstr "ุฃุฌู‡ุฒุฉ ุฃุฎุฑู‰" #~ msgid "- None -" #~ msgstr "- ู„ุง ุดูŠุก -" dino-0.1.0/plugins/omemo/po/ca.po0000644000000000000000000001653013614354364015313 0ustar rootroot# Catalan translation for dino-omemo. # This file is distributed under the same license as the dino package. # Jordi Mallach , 2018. # msgid "" msgstr "" "Project-Id-Version: dino-omemo 20180123\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2020-01-14 05:21+0000\n" "Language-Team: Catalan \n" "Language: ca\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.11-dev\n" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Gestiona" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Aquest contacte tรฉ dispositius nous" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Heu afegit un dispositiu nou per al compte %s?" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Gestiona la clau" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contacts device." msgstr "" "Compareu l'empremta, carร cter per carร cter, amb la que es mostra al vostre " "dispositiu de contactes." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Not matching" msgstr "No coincideixen" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Matching" msgstr "Coincideixen" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Cancelยทla" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Confirma" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Comproveu la clau" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "highlighted accordingly in the chat window." msgstr "" "Desprรฉs de la confirmaciรณ, qualssevol missatges que enviรฏ %s amb aquesta " "clau es ressaltarร  a la finestra del xat." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Les empremtes no es corresponen" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Cercioreu-vos que estigueu verificant l'empremta correcta. Si no en " "coincideixen, el compte de %s podria trobar-se en perill i heu de considerar " "rebutjar aquesta clau." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 #, fuzzy msgid "Verify key fingerprint" msgstr "Emprempta prรฒpia" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Rebutja la clau" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Stop accepting this key during communication with its associated contact." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Accepta la clau" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Start accepting this key during communication with its associated contact" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Actualment, aquesta clau ha estat %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "acceptada" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send messages." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "verificada" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "rebutjada" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to receive messages, and any messages " "sent by it will be ignored." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "ignored and none of your messages will be readable using this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "Once confirmed this key will be usable by %s to receive and send messages." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Enrere" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "Gestiรณ de claus OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Accepta claus noves automร ticament" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "" "When this contact adds new encryption keys to their account, automatically " "accept them." msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Clau prรฒpia" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Claus noves" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Claus associades" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Claus inactives" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "When you add new encryption keys to your account, automatically accept them." msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Acceptada" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Rebutjada" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Verificada" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "No utilitzada" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Empremta prรฒpia" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connect" msgstr "Es generarร  durant la primera connexiรณ" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Xifratge" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d dispositiu OMEMO" msgstr[1] "%d dispositius OMEMO" #~ msgid "Unknown device (0x%.8x)" #~ msgstr "Dispositiu desconegut (0x%.8x)" #~ msgid "Other devices" #~ msgstr "Altres dispositius" #~ msgid "- None -" #~ msgstr "- Cap -" dino-0.1.0/plugins/omemo/po/de.po0000644000000000000000000002145613614354364015323 0ustar rootrootmsgid "" msgstr "" "Project-Id-Version: dino-omemo-0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2020-01-20 21:21+0000\n" "Language-Team: German \n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.11-dev\n" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Verwalten" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Dieser Kontakt hat neue Gerรคte" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "OMEMO-Vertrauensentscheidung erforderlich" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Hast du ein neues Gerรคt fรผr den Account %s hinzugefรผgt?" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Schlรผssel verwalten" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contacts device." msgstr "" "Vergleichen Sie den Fingerabdruck, Buchstabe fรผr Buchstabe, mit dem auf dem " "Gerรคt Ihres Kontakts." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Not matching" msgstr "Nicht รผbereinstimmend" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Matching" msgstr "Stimmt รผberein" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Abbrechen" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Bestรคtigen" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Schlรผssel verifizieren" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "highlighted accordingly in the chat window." msgstr "" "Nach der Bestรคtigung werden alle mit diesem Schlรผssel verschlรผsselten " "Nachrichten von %s hervorgehoben." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Fingerabdrรผcke stimmen nicht รผberein" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Bitte รผberprรผfe, ob du den richtigen Fingerabdruck vergleichst. Wenn die " "Fingerabdrรผcke nicht รผbereinstimmen, ist das Konto von %s mรถglicherweise " "kompromittiert und du solltest in Betracht ziehen, den Schlรผssel abzulehnen." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Fingerabdruck รผberprรผfen" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Diesen Fingerabdruck mit dem Fingerabdruck vergleichen, der auf dem Gerรคt " "deines Kontaktes angezeigt wird." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Schlรผssel ablehnen" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Stop accepting this key during communication with its associated contact." msgstr "" "Diesen Schlรผssel nicht mehr fรผr die Kommunikation mit dem Schlรผsselbesitzer " "akzeptieren." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Schlรผssel akzeptieren" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Start accepting this key during communication with its associated contact" msgstr "" "Akzeptiere diesen Schlรผssel fรผr die Kommunikation mit dem zugehรถrigen Kontakt" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Dieser Schlรผssel ist aktuell %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "akzeptiert" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send messages." msgstr "" "Das bedeutet, er kann von %s benutzt werden um Nachrichten zu empfangen und " "versenden." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "รผberprรผft" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "Darรผber hinaus wurde รผberprรผft, dass er mit dem Schlรผssel auf dem Gerรคt des " "Kontaktes รผbereinstimmt." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "abgelehnt" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to receive messages, and any messages " "sent by it will be ignored." msgstr "" "Das bedeutet, dass es von %s nicht verwendet werden kann, um Nachrichten zu " "empfangen und alle von ihm gesendeten Nachrichten werden ignoriert." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "ignored and none of your messages will be readable using this key." msgstr "" "Nach der Bestรคtigung werden alle zukรผnftigen Nachrichten, die von %s mit " "diesem Schlรผssel gesendet werden, ignoriert und keine Ihrer Nachrichten ist " "mit diesem Schlรผssel lesbar." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "Once confirmed this key will be usable by %s to receive and send messages." msgstr "" "Sobald bestรคtigt, kann %s diesen Schlรผssel zum Senden und Empfangen von " "Nachrichten verwenden." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Zurรผck" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "OMEMO-Schlรผssel Verwaltung" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Neue Schlรผssel automatisch akzeptieren" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "" "When this contact adds new encryption keys to their account, automatically " "accept them." msgstr "" "Wenn dieser Kontakt neue Verschlรผsselungscodes zu seinem Konto hinzufรผgt, " "werden sie automatisch akzeptiert." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Eigener Schlรผssel" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Neue Schlรผssel" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Zugehรถrige Schlรผssel" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Inaktive Schlรผssel" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "When you add new encryption keys to your account, automatically accept them." msgstr "" "Wenn du einen neuen Schรผssel zu deinem Account hinzufรผgst, wird dieser " "automatisch akzeptiert." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Akzeptiert" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Abgelehnt" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "รœberprรผft" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Unbenutzt" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Eigener Fingerabdruck" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connect" msgstr "Wird beim ersten Verbinden erzeugt" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Verschlรผsselung" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d OMEMO Gerรคt" msgstr[1] "%d OMEMO Gerรคte" #~ msgid "" #~ "Start accepting this key during communication with its associated contact." #~ msgstr "" #~ "Diesen Schlรผssel fรผr die Kommunikation mit dem entsprechenden Kontakt " #~ "akzeptieren." #~ msgid "Unknown device (0x%.8x)" #~ msgstr "Unbekanntes Gerรคt (0x%.8x)" #~ msgid "Other devices" #~ msgstr "Andere Gerรคte" #~ msgid "- None -" #~ msgstr "- Keine -" #~ msgid "OMEMO" #~ msgstr "OMEMO" #~ msgid "Database error" #~ msgstr "Datenbank Fehler" dino-0.1.0/plugins/omemo/po/dino-omemo.pot0000644000000000000000000001441313614354364017155 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contacts device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Not matching" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Matching" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "highlighted accordingly in the chat window." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Stop accepting this key during communication with its associated contact." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Start accepting this key during communication with its associated contact" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send messages." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to receive messages, and any messages " "sent by it will be ignored." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "ignored and none of your messages will be readable using this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "Once confirmed this key will be usable by %s to receive and send messages." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "" "When this contact adds new encryption keys to their account, automatically " "accept them." msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "When you add new encryption keys to your account, automatically accept them." msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connect" msgstr "" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "" msgstr[1] "" dino-0.1.0/plugins/omemo/po/en.po0000644000000000000000000001360713614354364015334 0ustar rootrootmsgid "" msgstr "" "Project-Id-Version: dino-omemo-0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "Language: en\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contacts device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Not matching" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Matching" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "highlighted accordingly in the chat window." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Stop accepting this key during communication with its associated contact." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Start accepting this key during communication with its associated contact" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send messages." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to receive messages, and any messages " "sent by it will be ignored." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "ignored and none of your messages will be readable using this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "Once confirmed this key will be usable by %s to receive and send messages." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "" "When this contact adds new encryption keys to their account, automatically " "accept them." msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "When you add new encryption keys to your account, automatically accept them." msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connect" msgstr "" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "" msgstr[1] "" dino-0.1.0/plugins/omemo/po/es.po0000644000000000000000000002140213614354364015331 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2019-11-15 07:04+0000\n" "Language-Team: Spanish \n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.10-dev\n" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Gestionar" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Este contacto tiene un nuevo dispositivo" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "Decisiรณn de confianza requerida para clave OMEMO" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "ยฟAรฑadiste un nuevo dispositivo para la cuenta %s?" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Gestionar Clave" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contacts device." msgstr "" "Compara la huella digital, carรกcter a carรกcter, con la huella digital " "mostrada en el dispositivo de tus contactos." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Not matching" msgstr "No coincide" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Matching" msgstr "Coincide" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Cancelar" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Confirmar" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Verificar clave" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "highlighted accordingly in the chat window." msgstr "" "Una vez confirmada, cualquier futuro mensaje enviado por %s usando esta " "clave serรก resaltado en el ventana de conversaciรณn." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Huellas digitales no coinciden" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Por favor, verifica que estรกs comparando la huella digital correcta. Si la " "huella digital no coincide con la cuenta %s puede que la clave estรฉ " "comprometida y deberรญas considerar rechazar esta clave." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Verificar Huella Digital" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Compara esta huella digital con la huella digital mostrada en el dispositivo " "de tu contacto." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Rechazar clave" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Stop accepting this key during communication with its associated contact." msgstr "Dejar de aceptar esta clave durante la comunicaciรณn con este contacto." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Aceptar clave" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Start accepting this key during communication with its associated contact" msgstr "Aceptar esta clave durante la comunicaciรณn con este contacto" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Esta clave estรก actualmente %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "aceptada" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send messages." msgstr "" "Esto significa que puede ser usada por %s para recibir y enviar mensajes." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "verficada" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "Ademรกs, ha sido verificado que las claves coinciden con la clave del " "dispositivo de tu contacto." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "rechazada" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to receive messages, and any messages " "sent by it will be ignored." msgstr "" "Esto significa que no puede ser usada por %s para recibir mensajes, y " "cualquier mensaje enviado con esta clave serรก ignorado." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "ignored and none of your messages will be readable using this key." msgstr "" "Una vez confirmado, cualquier futuro mensaje enviado por %s usando esta " "clave serรก ignorado y ninguno de tus mensajes serรก legible usando esta clave." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "Once confirmed this key will be usable by %s to receive and send messages." msgstr "" "Una vez confirmada esta clave serรก usable por %s para recibir y enviar " "mensajes." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Volver" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "Huellas digitales OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Automรกticamente aceptar nuevas claves" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "" "When this contact adds new encryption keys to their account, automatically " "accept them." msgstr "" "Cuando este contacto aรฑada nuevas claves de cifrado a su cuenta, " "automรกticamente aceptarlas." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Mi clave" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Nuevas claves" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Claves asociadas" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Claves inactivas" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "When you add new encryption keys to your account, automatically accept them." msgstr "" "Cuando aรฑades nuevas claves de cifrado a tu cuenta, aceptarlas " "automรกticamente." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Aceptada" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Rechazada" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Verificada" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Sin usar" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Tu huella digital" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connect" msgstr "Serรก generada en la primera conexiรณn" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Cifrado" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d dispositivo OMEMO" msgstr[1] "%d dispositivos OMEMO" #~ msgid "" #~ "Start accepting this key during communication with its associated contact." #~ msgstr "Aceptar esta clave durante la comunicaciรณn con este contacto." #~ msgid "Reject Key" #~ msgstr "Rechazar clave" #~ msgid "Accept Key" #~ msgstr "Aceptar clave" #~ msgid "Unknown device (0x%.8x)" #~ msgstr "Dispositivo (0x%.8x) desconocido" #~ msgid "Other devices" #~ msgstr "Otros dispositivos" #~ msgid "- None -" #~ msgstr "- Ninguno -" #~ msgid "OMEMO" #~ msgstr "OMEMO" dino-0.1.0/plugins/omemo/po/eu.po0000644000000000000000000002122413614354364015335 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2019-01-05 11:06+0000\n" "Language-Team: Basque \n" "Language: eu\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.4-dev\n" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Kudeatu" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Kontaktu honek gailu berriak ditu" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "OMEMO konfiantza erabakia behar da" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Gailu berri gehitu duzu %s konturako?" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Gakoa kudeatu" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contacts device." msgstr "" "Hatz marka konparatu, hizkiz hizki, zure kontaktuaren gailuan ageri " "denarekin." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Not matching" msgstr "Ez datoz bat" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Matching" msgstr "Bat datoz" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Utzi" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Baieztatu" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Gakoa egiaztatu" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "highlighted accordingly in the chat window." msgstr "" "Egiaztatu ondoren, %s(e)k gako honekin etorkizunean bidalitako mezu guztiak " "behar bezala nabarmenduko dira txat leihoan." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Hatz markak ez datoz bat" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Mesedez egiaztatu ezazu hatz marka egokia konparatzen ari zarela. Hatz " "markak bat ez badatoz baliteke %s(e)ren kontua arriskuan egotea eta gako hau " "ez onartzea kontuan hartu beharko zenuke." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Gakoaren hatz marka egiaztatu" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Konpara ezazu gako honen hatz marka kontaktuaren gailuan agertzen den hatz " "markarekin." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Gakoa ukatu" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Stop accepting this key during communication with its associated contact." msgstr "" "Utzi gako hau onartzeari lotuta dagoen kontaktuarekin hitz egiten duzun " "bitartean." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Gakoa onartu" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Start accepting this key during communication with its associated contact" msgstr "" "Hasi gako hau onartzen lotuta dagoen kontaktuarekin hitz egiten duzun " "bitartean" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Gako hau %s dago une honetan." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "onartuta" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send messages." msgstr "" "Honek esan nahi du %s(e)k mezuak jaso eta bidaltzeko erabili dezakeela." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "egiaztatuta" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "Gainera zure kontaktuaren gailuan ageri den gakoarekin bat datorrela " "egiaztatu da." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "ukatuta" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to receive messages, and any messages " "sent by it will be ignored." msgstr "" "Honek esan nahi du %s(e)k ezin duela gakoa mezuak jasotzeko erabili, eta " "berak bidalitako edozein mezu alde batera utziko da." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "ignored and none of your messages will be readable using this key." msgstr "" "Egiaztatu ondoren, etorkizunean %s(e)k gako hau erabiliz bidalitako edozein " "mezu alde batera utziko da eta zure mezuetako bat ere ez da irakurgarria " "izango gako hau erabiliz." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "Once confirmed this key will be usable by %s to receive and send messages." msgstr "" "Egiaztatu ondoren %s(e)k gako hau mezuak jaso eta bidaltzeko erabili ahal " "izango du." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Atzera" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "OMEMO gakoen kudeaketa" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Gako berriak automatikoki onartu" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "" "When this contact adds new encryption keys to their account, automatically " "accept them." msgstr "" "Kontaktu honek enkriptatzeko gako berriak bere kontura gehitzen dituenean, " "automatikoki onartu." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Norberaren gakoa" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Gako berriak" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Lotutako gakoak" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "When you add new encryption keys to your account, automatically accept them." msgstr "" "Zure kontura enkriptazio gako berriak gehitzen dituzunean, automatikoki " "onartu." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Onartuta" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Ukatuta" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Egiaztatuta" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Ez erabilita" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Norberaren hatz marka" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connect" msgstr "Lehen konexioan sortuko da" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Enkriptazioa" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "OMEMO gailu %d" msgstr[1] "%d OMEMO gailu" #~ msgid "" #~ "Start accepting this key during communication with its associated contact." #~ msgstr "" #~ "Hasi gako hau onartzen lotuta dagoen kontaktuarekin hitz giten duzun " #~ "bitartean." #~ msgid "Reject Key" #~ msgstr "Gakoa baztertu" #~ msgid "Accept Key" #~ msgstr "Gakoa onartu" #~ msgid "Unknown device (0x%.8x)" #~ msgstr "Gailu ezezaguna (0x%.8x)" #~ msgid "Other devices" #~ msgstr "Beste gailuak" #~ msgid "- None -" #~ msgstr "- Bat ere ez -" dino-0.1.0/plugins/omemo/po/fi.po0000644000000000000000000001434213614354364015325 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2020-01-16 00:21+0000\n" "Language-Team: none\n" "Language: fi\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.11-dev\n" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contacts device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Not matching" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Matching" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Peruuta" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "highlighted accordingly in the chat window." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Stop accepting this key during communication with its associated contact." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Start accepting this key during communication with its associated contact" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send messages." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to receive messages, and any messages " "sent by it will be ignored." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "ignored and none of your messages will be readable using this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "Once confirmed this key will be usable by %s to receive and send messages." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "" "When this contact adds new encryption keys to their account, automatically " "accept them." msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "When you add new encryption keys to your account, automatically accept them." msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connect" msgstr "" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "" msgstr[1] "" dino-0.1.0/plugins/omemo/po/fr.po0000644000000000000000000002137213614354364015337 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2019-12-23 02:21+0000\n" "Language-Team: French \n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" "X-Generator: Weblate 3.10\n" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Gรฉrer" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Ce contact a des nouveaux appareils" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "Dรฉcision de confiance pour OMEMO requise" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Avez-vous ajoutรฉ un nouvel appareil pour le compte %s ?" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Gรฉrer la clรฉ" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contacts device." msgstr "" "Comparez l'empreinte digitale, lettre par lettre, avec celle affichรฉe sur " "l'appareil de votre contact." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Not matching" msgstr "Ne correspond pas" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Matching" msgstr "Correspond" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Annuler" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Confirmer" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Vรฉrifier la clรฉ" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "highlighted accordingly in the chat window." msgstr "" "Aprรจs confirmation, tout futur message envoyรฉ par %s utilisant cette clรฉ " "sera surlignรฉ dans la fenรชtre de conversation." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Les empreintes digitales ne correspondent pas" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Vรฉrifiez s'il vous plaรฎt que vous comparez la bonne empreinte digitale. Si " "les empreintes ne correspondent pas, le compte de %s pourrait รชtre compromis " "et vous devriez envisager de rejeter cette clรฉ." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Vรฉrifier l'empreinte digitale" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Comparez cette empreinte digitale avec l'empreinte affichรฉe sur l'appareil " "du contact." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Rejeter la clรฉ" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Stop accepting this key during communication with its associated contact." msgstr "" "Arrรชter d'accepter cette clรฉ durant la communication avec son contact " "associรฉ." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Accepter la clรฉ" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Start accepting this key during communication with its associated contact" msgstr "Accepter cette clรฉ durant la communication avec son contact associรฉ" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Cette clรฉ est actuellement %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "acceptรฉe" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send messages." msgstr "" "Cela signifie qu'elle peut รชtre utilisรฉe par %s pour recevoir et envoyer des " "messages." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "vรฉrifiรฉe" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "De plus, il a รฉtรฉ vรฉrifiรฉ que la clรฉ coรฏncide avec celle de l'appareil du " "contact." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "rejetรฉe" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to receive messages, and any messages " "sent by it will be ignored." msgstr "" "Cela signifie qu'elle ne peut pas รชtre utilisรฉe par %s pour recevoir des " "messages, et que les messages envoyรฉs seront ignorรฉs." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "ignored and none of your messages will be readable using this key." msgstr "" "Aprรจs confirmation, tout futur message envoyรฉ par %s utilisant cette clรฉ " "sera ignorรฉ et vos messages ne seront pas lisibles en utilisant cette clรฉ." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "Once confirmed this key will be usable by %s to receive and send messages." msgstr "" "Aprรจs confirmation, cette clรฉ sera utilisable par %s pour recevoir et " "envoyer des messages." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Retour" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "Gestion des clรฉs OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Accepter automatiquement les nouvelles clรฉs" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "" "When this contact adds new encryption keys to their account, automatically " "accept them." msgstr "" "Quand ce contact ajoute une nouvelle clรฉ de chiffrement ร  son compte, " "l'accepter automatiquement." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Clรฉ personnelle" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Nouvelles clรฉs" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Clรฉs associรฉes" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Clรฉs inactives" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "When you add new encryption keys to your account, automatically accept them." msgstr "" "Accepter automatiquement les nouvelles clรฉs de chiffrements que vous ajoutez " "ร  votre compte." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Acceptรฉe" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Rejetรฉe" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Vรฉrifiรฉe" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Inutilisรฉ" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Empreinte digitale personnelle" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connect" msgstr "Sera gรฉnรฉrรฉ lors de la premiรจre connexion" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Chiffrement" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d pรฉriphรฉrique OMEMO" msgstr[1] "%d pรฉriphรฉriques OMEMO" #~ msgid "" #~ "Start accepting this key during communication with its associated contact." #~ msgstr "" #~ "Accepter cette clรฉ durant la communication avec son contact associรฉ." #~ msgid "Unknown device (0x%.8x)" #~ msgstr "Pรฉriphรฉrique inconnu (0x%.8x)" #~ msgid "Other devices" #~ msgstr "Autres pรฉriphรฉriques" #~ msgid "- None -" #~ msgstr "- Aucun -" dino-0.1.0/plugins/omemo/po/gl.po0000644000000000000000000002113013614354364015322 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2019-12-11 03:27+0000\n" "Language-Team: Galician \n" "Language: gl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.10-dev\n" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Xestionar" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Este contacto ten novos dispositivos" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "Precisa decidir sobre a confianza OMEMO" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Engadeu un novo dispositivo para a conta %s?" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Xestionar chave" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contacts device." msgstr "" "Comparar pegada dixital, caracter a caracter, coa mostrada no dispositivo do " "seu contacto." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Not matching" msgstr "Non coinciden" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Matching" msgstr "Coinciden" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Cancelar" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Confirmar" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Validar chave" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "highlighted accordingly in the chat window." msgstr "" "Unha vez confirmada, as futuras mensaxes enviadas por %s utilizando esta " "chave serรก resaltada acorde na ventรก de conversa." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Non coinciden as pegadas dixitais" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Por favor, comprobe que estรก coparando a pegada correcta. Si as pegadas " "dixitais non coinciden, a conta de %s poderรญa estar comprometida e deberรญa " "considerar rexeitar esta chave." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Validar a chave de pegada dixital" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Compare a pegada dixital de esta chave coa pegada mostrada no dispositivo do " "contacto." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Rexeitar chave" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Stop accepting this key during communication with its associated contact." msgstr "" "Deixar de aceptar esta chave durante a comunicaciรณn co contacto asociado a " "ela." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Aceptar chave" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Start accepting this key during communication with its associated contact" msgstr "" "Comezar aceptando esta chave durante a comunicaciรณn co seu contacto asociado" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Esta chave estรก actualmente %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "aceptada" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send messages." msgstr "" "Esto significa que pode ser utilizada por %s para recibir e enviar mensaxes." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "verficada" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "Adicionalmente foi validada e coincide coa chave no dispositivo do contacto." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "rexeitada" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to receive messages, and any messages " "sent by it will be ignored." msgstr "" "Esto significa que non pode ser utilizada por %s para recibir mensaxes, e " "calquer mensaxe enviada con ela serรก ignorado." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "ignored and none of your messages will be readable using this key." msgstr "" "Unha vez confirmada, calquer mensaxe futura enviada por %s utilizando esta " "chave serรก ignorada e ningunha das sรบas mensaxes serรกn lexibles usando esta " "chave." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "Once confirmed this key will be usable by %s to receive and send messages." msgstr "" "Unha vez confirmada esta chave serรก utilizable por %s para recibir e enviar " "mensaxes." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Atrรกs" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "Xestiรณn das chaves OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Aceptar automรกticamente novas chaves" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "" "When this contact adds new encryption keys to their account, automatically " "accept them." msgstr "" "Cando este contacto engade novas chaves de cifrado a sรบa conta, aceptalas " "automรกticamente." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Chave propia" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Novas chaves" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Chaves asociadas" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Chaves non activas" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "When you add new encryption keys to your account, automatically accept them." msgstr "" "Cando engade novas chaves de cifrado a sรบa conta, aceptalas automรกticamente." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Aceptada" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Rexeitada" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Validada" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Sen usar" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Pegada propia" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connect" msgstr "Crearase na primeira conexiรณn" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Cifrado" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d dispositivo OMEMO" msgstr[1] "%d dispositivos OMEMO" #~ msgid "" #~ "Start accepting this key during communication with its associated contact." #~ msgstr "" #~ "Comezar a aceptar esta chave nas comunicaciรณns co contacto asociado a ela." #~ msgid "Unknown device (0x%.8x)" #~ msgstr "Dispositivo descoรฑecido (0x%.8x)" #~ msgid "Other devices" #~ msgstr "Outros dispositivos" #~ msgid "- None -" #~ msgstr "-Nada-" dino-0.1.0/plugins/omemo/po/hu.po0000644000000000000000000001515613614354364015347 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2020-01-16 00:21+0000\n" "Language-Team: Hungarian \n" "Language: hu\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.11-dev\n" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contacts device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Not matching" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Matching" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Mรฉgse" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "highlighted accordingly in the chat window." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 #, fuzzy msgid "Verify key fingerprint" msgstr "Sajรกt ujjlenyomat" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Stop accepting this key during communication with its associated contact." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Start accepting this key during communication with its associated contact" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send messages." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to receive messages, and any messages " "sent by it will be ignored." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "ignored and none of your messages will be readable using this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "Once confirmed this key will be usable by %s to receive and send messages." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 #, fuzzy msgid "OMEMO Key Management" msgstr "OMEMO kulcsok" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "" "When this contact adds new encryption keys to their account, automatically " "accept them." msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "When you add new encryption keys to your account, automatically accept them." msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Sajรกt ujjlenyomat" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connect" msgstr "Az elsล‘ csatlakozรกskor lesz lรฉtrehozva" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Titkosรญtรกs" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d OMEMO eszkรถz" msgstr[1] "%d OMEMO eszkรถz" #~ msgid "Unknown device (0x%.8x)" #~ msgstr "Ismeretlen eszkรถz (0x%.8x)" #~ msgid "Other devices" #~ msgstr "Tovรกbbi eszkรถzรถk" #~ msgid "- None -" #~ msgstr "- Nincs -" dino-0.1.0/plugins/omemo/po/it.po0000644000000000000000000002150113614354364015336 0ustar rootrootmsgid "" msgstr "" "Project-Id-Version: dino-omemo-0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2019-12-12 20:05+0000\n" "Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.10-dev\n" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Gestisci" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Questo contatto ha dei nuovi dispositivi" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "Una decisione sulla fiducia รจ necessaria per la chiave OMEMO" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Hai aggiunto un nuovo dispositivo per l'account %s?" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Gestisci la chiave" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contacts device." msgstr "" "Confronta la fingerprint, carattere per carattere, con quella mostrata sul " "dispositivo del tuo contatto." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Not matching" msgstr "Non corrispondenti" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Matching" msgstr "Corrispondenti" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Annulla" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Conferma" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Verifica la chiave" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "highlighted accordingly in the chat window." msgstr "" "Una volta confermato, qualsiasi messaggio futuro inviato da %s usando questa " "chiave sarร  evidenziato di conseguenza nella finestra della conversazione." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Le fingerprint non corrispondono" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Si prega di verificare che si stia confrontanto la fingerprint corretta. Se " "le fingerprint non corrispondono, l'account di %s potrebbe essere " "compromesso e si dovrebbe rifiutare questa chiave." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Verifica la fingerprint della chiave" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Confronta la fingerprint di questa chiave con quella mostrata sul " "dispositivo del contatto." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Chiave rifiutata" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Stop accepting this key during communication with its associated contact." msgstr "" "Smetti di accettare questa chiave durante le comunicazioni col contatto " "associato." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Chiave accettata" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Start accepting this key during communication with its associated contact" msgstr "" "Inizia ad accettare questa chiave durante le comunicazioni con il contatto " "ad essa associato" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "La chiave attualmente รจ %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "accettata" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send messages." msgstr "" "Questo vuol dire che puรฒ essere usata da %s per ricevere ed inviare messaggi." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "verificata" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "Inoltre รจ stato verificato che corrisponda con la chiave sul dispositivo del " "contatto." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "rifiutata" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to receive messages, and any messages " "sent by it will be ignored." msgstr "" "Questo vuol dire che non puรฒ essere usata da %s per ricevere messaggi, ed " "ogni messaggio inviato attraverso di essa sarร  ignorato." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "ignored and none of your messages will be readable using this key." msgstr "" "Una volta confermato, ogni messaggio futuro inviato da %s usando questa " "chiave sarร  ignorato e nessuno dei tuoi messaggi sarร  leggibile usando " "questa chiave." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "Once confirmed this key will be usable by %s to receive and send messages." msgstr "" "Una volta confermata questa chiave sarร  utilizzabile da %s per ricevere ed " "inviare messaggi." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Indietro" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "Gestione delle chiavi OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Accetta automaticamente le nuove chiavi" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "" "When this contact adds new encryption keys to their account, automatically " "accept them." msgstr "" "Quando questo contatto aggiunge nuove chiavi di cifratura al proprio " "account, accettale automaticamente." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Proprie chiavi" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Nuove chiavi" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Chiavi associate" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Chiavi inattive" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "When you add new encryption keys to your account, automatically accept them." msgstr "" "Quando aggiungi nuove chiavi di cifratura al tuo account, accettale " "automaticamente." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Accettata" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Rifiutata" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Verificata" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Inutilizzata" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Propria impronta" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connect" msgstr "Verrร  generata alla prima connessione" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Crittografia" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d dispositivo OMEMO" msgstr[1] "%d dispositivi OMEMO" #~ msgid "" #~ "Start accepting this key during communication with its associated contact." #~ msgstr "" #~ "Inizia ad accettare questa chiave durante le comunicazioni col contatto " #~ "associato" #~ msgid "Reject Key" #~ msgstr "Rifiuta la chiave" #~ msgid "Accept Key" #~ msgstr "Accetta la chiave" #~ msgid "Unknown device (0x%.8x)" #~ msgstr "Dispositivo sconosciuto (0x%.8x)" #~ msgid "Other devices" #~ msgstr "Altri dispositivi" #~ msgid "- None -" #~ msgstr "- Nessuno -" #~ msgid "OMEMO" #~ msgstr "OMEMO" #~ msgid "Database error" #~ msgstr "Errore del database" dino-0.1.0/plugins/omemo/po/ja.po0000644000000000000000000001730213614354364015320 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2020-01-28 23:24+0000\n" "Language-Team: none\n" "Language: ja\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 3.11-dev\n" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "็ฎก็†" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "ใ“ใฎ้€ฃ็ตกๅ…ˆใซๆ–ฐใ—ใ„ใƒ‡ใƒใ‚คใ‚นใŒ่ฟฝๅŠ ใ•ใ‚Œใพใ—ใŸ" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "OMEMO ไฟก้ ผ้ธๆŠžใŒๅฟ…่ฆใงใ™" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "ใ‚ขใ‚ซใ‚ฆใƒณใƒˆ %s ใซๆ–ฐใ—ใ„ใƒ‡ใƒใ‚คใ‚นใ‚’่ฟฝๅŠ ใ—ใพใ—ใŸใ‹๏ผŸ" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "ใ‚ญใƒผใ‚’็ฎก็†" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contacts device." msgstr "" "ไปฅไธ‹ใฎใƒ•ใ‚ฃใƒณใ‚ฌใƒผใƒ—ใƒชใƒณใƒˆใจใ€ใŠไฝฟใ„ใฎใƒ‡ใƒใ‚คใ‚นใซ่กจ็คบใ•ใ‚Œใฆใ„ใ‚‹ใƒ•ใ‚ฃใƒณใ‚ฌใƒผใƒ—ใƒชใƒณ" "ใƒˆใ‚’ใ€1ๆ–‡ๅญ—ใšใคๆฏ”่ผƒใ—ใฆใใ ใ•ใ„ใ€‚" #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Not matching" msgstr "ไธ€่‡ดใ—ใชใ„" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Matching" msgstr "ไธ€่‡ด" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "ใ‚ญใƒฃใƒณใ‚ปใƒซ" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "็ขบ่ช" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "ใ‚ญใƒผใ‚’็ขบ่ช" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "highlighted accordingly in the chat window." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "ใƒ•ใ‚ฃใƒณใ‚ฌใƒผใƒ—ใƒชใƒณใƒˆใŒไธ€่‡ดใ—ใพใ›ใ‚“" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "ใ‚ญใƒผใฎใƒ•ใ‚ฃใƒณใ‚ฌใƒผใƒ—ใƒชใƒณใƒˆใ‚’ๆคœ่จผ" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "ใ“ใฎใ‚ญใƒผใฎใƒ•ใ‚ฃใƒณใ‚ฌใƒผใƒ—ใƒชใƒณใƒˆใจใ€้€ฃ็ตกๅ…ˆใฎใƒ‡ใƒใ‚คใ‚นใซ่กจ็คบใ•ใ‚ŒใŸใƒ•ใ‚ฃใƒณใ‚ฌใƒผใƒ—ใƒชใƒณ" "ใƒˆใ‚’ๆฏ”่ผƒใ—ใพใ™ใ€‚" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "ใ‚ญใƒผใ‚’ๆ‹’ๅฆ" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Stop accepting this key during communication with its associated contact." msgstr "" "้–ข้€ฃไป˜ใ‘ใ‚‰ใ‚ŒใŸ้€ฃ็ตกๅ…ˆใจใƒˆใƒผใ‚ฏใ‚’ใ™ใ‚‹้š›ใซใ€ใ“ใฎใ‚ญใƒผใ‚’่จฑๅฏใ—ใชใ„ใ‚ˆใ†ใซใ—ใพใ™ใ€‚" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "ใ‚ญใƒผใ‚’่จฑๅฏ" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Start accepting this key during communication with its associated contact" msgstr "้–ข้€ฃไป˜ใ‘ใ‚‰ใ‚ŒใŸ้€ฃ็ตกๅ…ˆใจใƒˆใƒผใ‚ฏใ‚’ใ™ใ‚‹้š›ใซใ€ใ“ใฎใ‚ญใƒผใ‚’่จฑๅฏใ™ใ‚‹ใ‚ˆใ†ใซใ—ใพใ™" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "ใ“ใฎใ‚ญใƒผใฏ็พๅœจ%sใ€‚" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "่จฑๅฏใ•ใ‚Œใฆใ„ใพใ™" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send messages." msgstr "%s ใฏใ“ใฎใ‚ญใƒผใ‚’ไฝฟใฃใฆใƒกใƒƒใ‚ปใƒผใ‚ธใฎ้€ๅ—ไฟกใŒใงใใพใ™ใ€‚" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "ๆคœ่จผๆธˆใฟใงใ™" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "ๅŠ ใˆใฆใ€้€ฃ็ตกๅ…ˆใฎใƒ‡ใƒใ‚คใ‚นใฎใ‚ญใƒผใจไธ€่‡ดใ™ใ‚‹ใ“ใจใ‚’ๆคœ่จผๆธˆใฟใงใ™ใ€‚" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "ๆ‹’ๅฆใ•ใ‚Œใฆใ„ใพใ™" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to receive messages, and any messages " "sent by it will be ignored." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "ignored and none of your messages will be readable using this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "Once confirmed this key will be usable by %s to receive and send messages." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "ๆˆปใ‚‹" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "OMEMO ใ‚ญใƒผ็ฎก็†" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "่‡ชๅ‹•็š„ใซๆ–ฐใ—ใ„ใ‚ญใƒผใ‚’่จฑๅฏ" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "" "When this contact adds new encryption keys to their account, automatically " "accept them." msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "่‡ช่บซใฎใ‚ญใƒผ" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "ๆ–ฐใ—ใ„ใ‚ญใƒผ" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "้–ข้€ฃไป˜ใ‘ใ‚‰ใ‚ŒใŸใ‚ญใƒผ" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "้žใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–ใชใ‚ญใƒผ" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "When you add new encryption keys to your account, automatically accept them." msgstr "ใ‚ขใ‚ซใ‚ฆใƒณใƒˆใซ่ฟฝๅŠ ใ—ใŸๆ–ฐใ—ใ„ๆš—ๅทๅŒ–ใ‚ญใƒผใ‚’่‡ชๅ‹•็š„ใซ่จฑๅฏใ—ใพใ™ใ€‚" #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "่จฑๅฏ" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "ๆ‹’ๅฆ" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "ๆคœ่จผๆธˆใฟ" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "ๆœชไฝฟ็”จ" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "่‡ช่บซใฎใƒ•ใ‚ฃใƒณใ‚ฌใƒผใƒ—ใƒชใƒณใƒˆ" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connect" msgstr "ๆœ€ๅˆใฎๆŽฅ็ถšใง็”Ÿๆˆใ•ใ‚Œใพใ™" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "ๆš—ๅทๅŒ–" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d ๅฐใฎ OMEMO ใƒ‡ใƒใ‚คใ‚น" dino-0.1.0/plugins/omemo/po/lb.po0000644000000000000000000002174213614354364015326 0ustar rootrootmsgid "" msgstr "" "Project-Id-Version: dino-omemo-0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2019-12-21 12:21+0000\n" "Language-Team: Luxembourgish \n" "Language: lb\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.10\n" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Verwalten" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Dรซse Kontakt huet nei Gerรคter" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "OMEMO Vertrauens Entscheedungย noutwenneg" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Hues du een neit Gerรคt fir %s dobรคi gemaach?" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Schlรซssel verwalten" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contacts device." msgstr "" "Verglรคich de Fangerofdrock, Zeecheย fir Zeechen, mat dem den um Gerรคt vum " "Kontakt ugewiseย gรซtt." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Not matching" msgstr "Stemmt net iwwerteneen" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Matching" msgstr "Stemmt iwwerteneen" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Ofbriechen" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Confirmรฉieren" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Schlรซssel verifizรฉieren" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "highlighted accordingly in the chat window." msgstr "" "Wann konformรฉiert, all zukรผnfteg Messagen dรฉi vum %s mat dรซsem Schlรซssel " "geschรฉcktย ginn, ginn deementspriechendย am Chatfenster markรฉiert." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Fangerofdrรฉck stรซmmenย net iwwerteneen" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Wann ech geliftย iwwerprรฉif, ob's du mat dem " "korrekteย Fangerofdrockย verglรคichs. Wann Fangerofdrรฉckย net iwwerteneestรซmmen, " "dann ass dem %s sรคiย Konto villรคicht kompromettรฉiertย anย du solls dรซse " "Schlรซsselย verweigeren." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Iwwerprรฉif Schlรซsselย Fangerofdrock" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Verglรคich de Fangerofdrock vun dรซsem Schlรซsselย mam Fangerofdrockย den um " "Gerรคt vun dรซsemย Kontakt steet." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Schlรซssel ofleenen" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Stop accepting this key during communication with its associated contact." msgstr "" "Dรซse Schlรซsselย net mรฉi akzeptรฉiereย wรคrendย der Kommunikatiounย mat sengem " "verbonneneย Kontakt." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Schlรซssel acceptรฉieren" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Start accepting this key during communication with its associated contact" msgstr "Dรซse Schlรซssel wรคrendย de Conversatioun mat sengem Kontakt benotzen" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Den aktuelle Schlรซsselย ass %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "akzeptรฉiert" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send messages." msgstr "" "Dat bedeit, dass et vum %s ka benotzt giย fir Messagen ze empfรคnken an ze " "verschรฉcken." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "verifizรฉiert" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "De Schlรซsselย ass zousรคtzlech iwwerprรฉift giย mam Schlรซsselย um Gerรคt vum " "Kontakt iwwerteneen ze stรซmmen." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "ofgeleent" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to receive messages, and any messages " "sent by it will be ignored." msgstr "" "Dat bedeit, dass dรซse Schlรซsselย net kaย vum %s benotz giย fir Messagen ze " "empfรคnken anย all Message dรฉi geschรฉckt giย ginn ignorรฉiert." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "ignored and none of your messages will be readable using this key." msgstr "" "Wann confirmรฉiert, all zukรผnfteg Message dรฉi vum %s mat dรซsem Schlรซssel " "geschรฉcktย ginn, ginn ignorรฉiert aย keng vun dengem Messagรซย wรคerteย liesbar " "sinn." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "Once confirmed this key will be usable by %s to receive and send messages." msgstr "" "Wann konformรฉiertย ass de Schlรซsselย vum %s benotzbar fir Messagen ze " "empfรคnken an ze schรฉcken." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Zerรฉck" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "OMEMO Schlรซsselenverwaltung" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Automatesch nei Schlรซsselenย akzeptรฉieren" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "" "When this contact adds new encryption keys to their account, automatically " "accept them." msgstr "" "Wann dรซseย Kontakt nรฉi Schlรซsselenย zu sengem Konto bรคifรผรผgt, dรฉi automatesch " "akzeptรฉieren." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Eegeneย Schlรซssel" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Nei Schlรซsselen" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Zougehรฉiereg Schlรซsselen" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Inaktiv Schlรซsselen" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "When you add new encryption keys to your account, automatically accept them." msgstr "" "Wann s du nei Schlรซsseleย bei dรคin Account bรคifรผรผgs, dรฉi automatesch " "akzeptรฉieren." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Akzeptรฉiert" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Ofgeleent" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Verifizรฉiert" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Onbenotzt" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Eegene Fangerofdrock" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connect" msgstr "Gett bei der รฉischterย Connectioun generรฉiert" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Verschlรซsselung" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d OMEMO Gerรคt" msgstr[1] "%d OMEMO Gerรคter" #~ msgid "" #~ "Start accepting this key during communication with its associated contact." #~ msgstr "" #~ "Dรซse Schlรซsselย wรคrendย der Kommunikatioun mat sengem verbonneย Kontakt " #~ "akzeptรฉieren." #~ msgid "Reject Key" #~ msgstr "Schlรซssel verweigeren" #~ msgid "Accept Key" #~ msgstr "Schlรซssel acceptรฉieren" #~ msgid "Unknown device (0x%.8x)" #~ msgstr "Onbekannt Gerรคt (0x%.8x)" #~ msgid "Other devices" #~ msgstr "Aner Gerรคter" #~ msgid "- None -" #~ msgstr "- Keng -" #~ msgid "OMEMO" #~ msgstr "OMEMO" #~ msgid "Database error" #~ msgstr "Datebank Feeler" dino-0.1.0/plugins/omemo/po/nb.po0000644000000000000000000002125213614354364015324 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2019-12-11 03:27+0000\n" "Language-Team: Norwegian Bokmรฅl \n" "Language: nb\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.10-dev\n" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Behandle" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Denne kontakten har nye enheter" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "OMEMO tillitsbeslutning kreves" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "La du til en ny enhet for kontoen %s?" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Hรฅndter nรธkkel" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contacts device." msgstr "" "Sammenlign fingeravtrykket, tegn for tegn, med den vist pรฅ din kontakts " "enhet." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Not matching" msgstr "Samsvarer ikke" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Matching" msgstr "Samsvarer" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Avbryt" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Bekreft" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Bekreft nรธkkel" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "highlighted accordingly in the chat window." msgstr "" "Nรฅr fรธrst bekreftet, vil fremtidige meldinger sendt av %s ved bruk av denne " "nรธkkelen bli fremhevet tilsvarende i sludrevinduet." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Fingeravtrykkene stemmer ikke overens" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Sjekk at du sammenligner riktig fingeravtrykk. Hvis de ikke stemmer overens, " "kan det hende %s sin konto har falt i gale hender, og du bรธr overveie รฅ " "avvise denne nรธkkelen." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Sjekk nรธkkelfingeravtrykk" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Sammenlign dette fingeravtrykket med fingeravtrykket vist pรฅ kontaktens " "enhet." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Avslรฅ nรธkkel" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Stop accepting this key during communication with its associated contact." msgstr "" "Slutt รฅ godta denne nรธkkelen under kommunikasjon med dens tilknyttede " "kontakt." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Godta nรธkkel" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Start accepting this key during communication with its associated contact" msgstr "" "Aksepter heretter denne nรธkkelen i kommunikasjon med dens tilhรธrende kontakt" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Denne nรธkkelen er i รธyeblikket %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "godtatt" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send messages." msgstr "Dette betyr at den kan brukes av %s til รฅ motta og sende meldinger." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "bekreftet" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "I tillegg har den blitt bekreftet รฅ samsvare med nรธkkelen pรฅ kontaktens " "enhet." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "avslรฅtt" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to receive messages, and any messages " "sent by it will be ignored." msgstr "" "Dette betyr at den ikke kan brukes av %s til รฅ motta meldinger, og enhver " "melding sendt pรฅ vegne av den vil bli ignorert." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "ignored and none of your messages will be readable using this key." msgstr "" "Etter bekreftelsen vil fremtidige meldinger sendt av %s der denne nรธkkelen " "brukes bli ignorert, og ingen av dine meldinger vil kunne leses med den." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "Once confirmed this key will be usable by %s to receive and send messages." msgstr "" "Nรฅr fรธrst bekreftet vil denne nรธkkelen kunne brukes av %s til รฅ motta og " "sende meldinger." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Tilbake" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "OMEMO-nรธkkelhรฅndtering" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Godta nye nรธkler automatisk" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "" "When this contact adds new encryption keys to their account, automatically " "accept them." msgstr "" "Nรฅr denne kontakten legger nye krypteringsnรธkler til kontoen sin, godta dem " "automatisk." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Egen nรธkkel" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Nye nรธkler" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Tilknyttede nรธkler" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Inaktive nรธkler" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "When you add new encryption keys to your account, automatically accept them." msgstr "" "Godta dem automatisk nรฅr du legger nye krypteringsnรธkler til kontoen din." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Godtatt" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Avslรฅtt" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Bekreftet" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Ubrukt" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Eget fingeravtrykk" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connect" msgstr "Vil bli generert ved fรธrste tilkobling" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Kryptering" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d OMEMO-enhet" msgstr[1] "%d OMEMO-enheter" #~ msgid "" #~ "Start accepting this key during communication with its associated contact." #~ msgstr "" #~ "Begynn รฅ godta denne nรธkkelen under kommunikasjon med dens tilknyttede " #~ "kontakt." #~ msgid "Reject Key" #~ msgstr "Avslรฅ nรธkkel" #~ msgid "Accept Key" #~ msgstr "Godta nรธkkel" #~ msgid "Unknown device (0x%.8x)" #~ msgstr "Ukjent enhet (0x%.8x)" #~ msgid "Other devices" #~ msgstr "Andre enheter" #~ msgid "- None -" #~ msgstr "- Ingen -" #~ msgid "OMEMO" #~ msgstr "OMEMO" dino-0.1.0/plugins/omemo/po/nl.po0000644000000000000000000002133313614354364015336 0ustar rootrootmsgid "" msgstr "" "Project-Id-Version: dino-omemo-0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2020-01-16 00:21+0000\n" "Language-Team: Dutch \n" "Language: nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.11-dev\n" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Beheren" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Dit contact heeft nieuwe apparaten" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "OMEMO-vertrouwenskeuze vereist" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Gebruik je een nieuw apparaat met account %s?" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Sleutel beheren" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contacts device." msgstr "" "Vergelijk de fingerafdruk, letter voor letter, met die op het apparaat van " "je contact." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Not matching" msgstr "Komen niet overeen" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Matching" msgstr "Komen overeen" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Annuleren" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Bevestigen" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Sleutel verifiรซren" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "highlighted accordingly in the chat window." msgstr "" "Zodra bevestigd, worden nieuwe berichten verstuurd door %s met deze sleutel " "uitgelicht in het chatvenster." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Vingerafdrukken komen niet overeen" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Zorg ervoor dat je de juiste vingerafdrukken vergelijkt. Indien de " "vingerafdrukken niet overeenkomen kan het zijn dat het account van %s " "gecompromitteerd is. Overweeg in dat geval deze sleutel af te wijzen." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Vingerafdruk van sleutel verifiรซren" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Vergelijk de vingerafdruk van deze sleutel met de vingerafdruk die getoond " "wordt op het apparaat van je contact." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Sleutel afwijzen" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Stop accepting this key during communication with its associated contact." msgstr "" "Accepteer niet langer deze sleutel tijdens communicatie met het bijbehorende " "contact." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Sleutel accepteren" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Start accepting this key during communication with its associated contact" msgstr "" "Accepteer deze sleutel tijdens communicatie met het bijbehorende contact" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Deze sleutel is momenteel %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "geaccepteerd" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send messages." msgstr "" "Dit betekent dat %s het kan gebruiken om berichten te ontvangen en versturen." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "geverifieerd" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "Tevens komt het geverifieerd overeen met de sleutel op het apparaat van het " "contact." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "afgewezen" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to receive messages, and any messages " "sent by it will be ignored." msgstr "" "Dit betekent dat het niet gebruikt kan worden door %s om berichten te " "ontvangen. Berichten die hiermee verstuurd worden worden genegeerd." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "ignored and none of your messages will be readable using this key." msgstr "" "Zodra bevestigd, worden toekomstige berichten die verstuurd worden door %s " "met deze sleutel genegeerd en zullen geen van je berichten leesbaar zijn met " "deze sleutel." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "Once confirmed this key will be usable by %s to receive and send messages." msgstr "" "Zodra bevestigd kan deze sleutel gebruikt worden door %s om berichten te " "ontvangen en te versturen." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Terug" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "OMEMO-sleutelbeheer" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Automatisch nieuwe sleutels accepteren" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "" "When this contact adds new encryption keys to their account, automatically " "accept them." msgstr "" "Indien dit contact nieuwe encryptiesleutels toevoegt aan hun account, " "automatisch accepteren." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Eigen sleutel" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Nieuwe sleutels" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Geassocieerde sleutels" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Inactieve sleutels" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "When you add new encryption keys to your account, automatically accept them." msgstr "" "Nieuwe encryptiesleutels die aan het account worden toegevoegd automatisch " "accepteren." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Geaccepteerd" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Afgewezen" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Geverifieerd" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Ongebruikt" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Eigen vingerafdruk" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connect" msgstr "Wordt bij eerste verbinding gegenereerd" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Versleuteling" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d OMEMO-apparaat" msgstr[1] "%d OMEMO-apparaten" #, fuzzy #~ msgid "" #~ "Start accepting this key during communication with its associated contact." #~ msgstr "" #~ "Accepteer deze sleutel tijdens communicatie met het bijbehorende contact" #~ msgid "Reject Key" #~ msgstr "Sleutel Afwijzen" #~ msgid "Accept Key" #~ msgstr "Sleutel Aanvaarden" #~ msgid "Unknown device (0x%.8x)" #~ msgstr "Onbekend apparaat (0x%.8x)" #~ msgid "Other devices" #~ msgstr "Andere apparaten" #~ msgid "- None -" #~ msgstr "- Geen -" #~ msgid "OMEMO" #~ msgstr "OMEMO" #~ msgid "Database error" #~ msgstr "Databasefout" dino-0.1.0/plugins/omemo/po/nl_BE.po0000644000000000000000000002110413614354364015700 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2019-01-12 20:06+0000\n" "Language-Team: Flemish \n" "Language: nl_BE\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.4-dev\n" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Beheren" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Dit contact heeft nieuwe apparaten" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "OMEMO-vertrouwenskeuze vereist" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Gebruikt ge een nieuw apparaat met account %s?" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Sleutel beheren" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contacts device." msgstr "" "Vergelijkt de vingerafdruk, teken per teken, met die op het apparaat van uw " "contact." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Not matching" msgstr "Komen niet overeen" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Matching" msgstr "Komen overeen" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Annuleren" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Bevestigen" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Sleutel verifiรซren" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "highlighted accordingly in the chat window." msgstr "" "Zodra bevestigd, worden nieuwe berichten verstuurd door %s met deze sleutel " "uitgelicht in โ€™t chatvenster." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Vingerafdrukken komen niet overeen" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Zorgt ervoor dat ge de juiste vingerafdrukken vergelijkt. Indien dat de " "vingerafdrukken niet overeenkomen kan het zijn dat den account van %s " "gecompromitteerd is. Overweegt in dat geval van deze sleutel af te wijzen." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Vingerafdruk van sleutel verifiรซren" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Vergelijkt de vingerafdruk van deze sleutel met de vingerafdruk die dat " "wordt getoond op het apparaat van uw contact." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Sleutel afwijzen" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Stop accepting this key during communication with its associated contact." msgstr "" "Aanvaardt deze sleutel niet meer tijdens communicatie met het bijbehorend " "contact." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Sleutel aanvaarden" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Start accepting this key during communication with its associated contact" msgstr "" "Aanvaardt deze sleutel tijdens communicatie met het bijbehorend contact" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Deze sleutel is momenteel %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "aanvaard" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send messages." msgstr "" "Dit betekent dat %s deze kan gebruiken voor berichten te ontvangen en " "versturen." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "geverifieerd" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "Bovendien komt het geverifieerd overeen met de sleutel op het apparaat van " "het contact." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "afgewezen" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to receive messages, and any messages " "sent by it will be ignored." msgstr "" "Dit betekent dat hij niet kan gebruikt worden door %s voor berichten te " "ontvangen. Berichten die dat hiermee worden verstuurd, worden genegeerd." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "ignored and none of your messages will be readable using this key." msgstr "" "Zodra bevestigd, worden toekomstige berichten die dat worden verstuurd door " "%s met deze sleutel genegeerd, en zullen geen van uw berichten met deze " "sleutel leesbaar zijn." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "Once confirmed this key will be usable by %s to receive and send messages." msgstr "" "Zodra bevestigd kan deze sleutel gebruikt worden door %s voor berichten te " "ontvangen en te versturen." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Terug" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "OMEMO-sleutelbeheer" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Nieuwe sleutels automatisch aanvaarden" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "" "When this contact adds new encryption keys to their account, automatically " "accept them." msgstr "" "Indien dat dit contact nieuwe versleutelingssleutels toevoegt aan zijn/haar " "account, deze automatisch aanvaarden." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Eigen sleutel" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Nieuwe sleutels" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Geassocieerde sleutels" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "When you add new encryption keys to your account, automatically accept them." msgstr "" "Nieuwe versleutelingssleutels die aan den account worden toegevoegd " "automatisch aanvaarden." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Aanvaard" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Afgewezen" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Geverifieerd" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Ongebruikt" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Eigen vingerafdruk" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connect" msgstr "Wordt bij de eerste verbinding gegenereerd" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Versleuteling" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d OMEMO-apparaat" msgstr[1] "%d OMEMO-apparaten" #~ msgid "Unknown device (0x%.8x)" #~ msgstr "Onbekend apparaat (0x%.8x)" #~ msgid "Other devices" #~ msgstr "Andere apparaten" #~ msgid "- None -" #~ msgstr "- Geen -" dino-0.1.0/plugins/omemo/po/pl.po0000644000000000000000000002117013614354364015337 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2020-01-22 22:21+0000\n" "Language-Team: Polish \n" "Language: pl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2;\n" "X-Generator: Weblate 3.11-dev\n" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Zarzฤ…dzaj" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Ten kontakt ma nowe urzฤ…dzenia" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "Decyzja o zaufaniu OMEMO jest potrzebna" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Czy dodaล‚eล› nowe urzฤ…dzenie dla konta %s?" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Zarzฤ…dzaj kluczem" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contacts device." msgstr "" "Porรณwnaj odcisk klucza, znak po znaku, z tym pokazanym na urzฤ…dzeniu twojego " "kontaktu." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Not matching" msgstr "Odciski nie zgadzajฤ… siฤ™" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Matching" msgstr "Odciski zgadzajฤ… siฤ™" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Anuluj" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Potwierdลบ" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Zweryfikuj klucz" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "highlighted accordingly in the chat window." msgstr "" "Przyszล‚e wiadomoล›ci wysล‚ane przez %s i szyfrowane tym kluczem bฤ™dฤ… " "odpowiednio zaznaczone w oknie czatu." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Odciski klucza nie zgadzajฤ… siฤ™" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Proszฤ™ zweryfikowaฤ‡, ลผe porรณwnujesz prawidล‚owy odcisk klucza. Jeล›li odciski " "nie zgadzajฤ… siฤ™, konto %s mogล‚o byฤ‡ zaatakowane i naleลผy rozwaลผyฤ‡ " "odrzucenie tego klucza." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Zweryfikuj odcisk klucza" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Porรณwnaj odcisk tego klucza z odciskiem wyล›wietlonym na urzฤ…dzeniu kontaktu." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Odrzuฤ‡ klucz" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Stop accepting this key during communication with its associated contact." msgstr "" "Zablokuj szyfrowanฤ… komunikacjฤ™ ze sprzฤ™tem kontaktu, ktรณry uลผywa ten klucz." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Zaakceptuj klucz" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Start accepting this key during communication with its associated contact" msgstr "" "Wล‚ฤ…cz szyfrowanฤ… komunikacjฤ™ ze sprzฤ™tem kontaktu, ktรณry uลผywa ten klucz." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Ten klucz jest obecnie %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "zaakceptowany" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send messages." msgstr "" "To znaczy, ลผe moลผe byฤ‡ uลผywany przez %s do odbierania i wysyล‚ania " "szyfrowanych wiadomoล›ci." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "zweryfikowany" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "Dodatkowo zostaล‚o sprawdzone, ลผe zgadza siฤ™ z kluczem na urzฤ…dzeniu kontaktu." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "odrzucony" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to receive messages, and any messages " "sent by it will be ignored." msgstr "" "To znaczy, ลผe nie moลผe byฤ‡ uลผywany przez %s do rozszyfrowania twoich " "wiadomoล›ci, a wiadomoล›ci szyfrowane nim nie bฤ™dฤ… u ciebie wyล›wietlane." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "ignored and none of your messages will be readable using this key." msgstr "" "Wiadomoล›ci wysล‚ane przez %s ze sprzฤ™tu uลผywajฤ…cego ten klucz nie bฤ™dฤ… " "wyล›wietlane. Podobnie sprzฤ™t tej osoby nie bฤ™dzie mรณgล‚ rozszyfrowaฤ‡ twoich " "wiadomoล›ci." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "Once confirmed this key will be usable by %s to receive and send messages." msgstr "" "To umoลผliwi wymianฤ™ szyfrowanych wiadomoล›ci ze sprzฤ™tem naleลผฤ…cym do %s, " "ktรณry uลผywaja ten klucz." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Wstecz" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "Zarzฤ…dzanie kluczami OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Akceptuj nowe klucze automatycznie" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "" "When this contact adds new encryption keys to their account, automatically " "accept them." msgstr "" "Gdy ten kontakt doda nowe klucze szyfrowania do swojego konta, zaakceptuj je " "automatycznie." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Wล‚asny klucz" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Nowe klucze" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Pozostaล‚e klucze" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Nieaktywne klucze" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "When you add new encryption keys to your account, automatically accept them." msgstr "" "Gdy dodam nowe klucze szyfrowania do mojego konta, zaakceptuj je " "automatycznie." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Zaakceptowany" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Odrzucony" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Zweryfikowany" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Nieuลผywany" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Wล‚asny odcisk klucza" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connect" msgstr "Zostanie wygenerowany przy pierwszym poล‚ฤ…czeniu" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Szyfrowanie" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d urzฤ…dzenie OMEMO" msgstr[1] "%d urzฤ…dzenia OMEMO" msgstr[2] "%d urzฤ…dzeล„ OMEMO" #~ msgid "Unknown device (0x%.8x)" #~ msgstr "Nieznane urzฤ…dzenie (0x%.8x)" #~ msgid "Other devices" #~ msgstr "Pozostaล‚e urzฤ…dzenia" #~ msgid "- None -" #~ msgstr "- Wybierz -" dino-0.1.0/plugins/omemo/po/pt_BR.po0000644000000000000000000002031013614354364015725 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2020-01-16 00:21+0000\n" "Language-Team: none\n" "Language: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" "X-Generator: Weblate 3.11-dev\n" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Gerenciar" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Esse contato possui novos dispositivos" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "Decisรฃo de confianรงa OMEMO necessรกria" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Vocรช adicionou um novo dispositivo para a conta %s?" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Gerenciar Chave" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contacts device." msgstr "" "Compare a impressรฃo digital, caractere a caractere, com aquela mostrada no " "dispositivo de seu contato." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Not matching" msgstr "Nรฃo correspondem" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Matching" msgstr "Correspondem" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Cancelar" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Confirmar" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Verificar chave" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "highlighted accordingly in the chat window." msgstr "" "Uma vez confirmada, toda mensagem enviada por %s no futuro serรก destacada na " "janela da conversa." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Impressรตes digitais nรฃo correspondem" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Por favor verifique se vocรช estรก comparando as impressรตes digitais corretas. " "Caso elas nรฃo correspondam, a conta do %s pode estar comprometida e vocรช " "deve considerar rejeitar essa chave." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Verificar impressรฃo digital da chave" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Compare a impressรฃo digital dessa chave com a impressรฃo digital exibida no " "dispositivo do contato." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Rejeitar chave" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Stop accepting this key during communication with its associated contact." msgstr "Pare de aceitar essa chave ao se comunicar com o contato associado." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Aceitar chave" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Start accepting this key during communication with its associated contact" msgstr "Aceite essa chave ao se comunicar com o contato associado" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Essa chave estรก atualmente %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "aceita" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send messages." msgstr "" "Isso significa que ela pode ser usada por %s para enviar e receber mensagens." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "verificada" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "Alรฉm disso, verificou-se que ela corresponde com a chave no dispositivo do " "contato." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "rejeitada" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to receive messages, and any messages " "sent by it will be ignored." msgstr "" "Isso significa que ela nรฃo pode ser usada por %s para receber mensagens e " "qualquer mensagem enviada com ela serรก ignorada." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "ignored and none of your messages will be readable using this key." msgstr "" "Uma vez confirmada, toda mensagem enviada por %s usando essa chave serรก " "ignorada e nenhuma de suas mensagens com ela serรฃo legรญveis." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "Once confirmed this key will be usable by %s to receive and send messages." msgstr "" "Uma vez confirmada, essa chave poderรก ser usada por %s para receber e enviar " "mensagens." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Voltar" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "Gerenciamento de Chave OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Aceitar novas chaves automaticamente" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "" "When this contact adds new encryption keys to their account, automatically " "accept them." msgstr "" "Quando esse contato adicionar novas chaves de criptografia ร  conta, aceite-" "as automaticamente." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Chave prรณpria" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Chaves novas" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Chaves associadas" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Chaves inativas" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "When you add new encryption keys to your account, automatically accept them." msgstr "" "Quando vocรช adicionar novas chaves de criptografia para sua conta, aceite-as " "automaticamente." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Aceita" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Rejeitada" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Verificada" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Nรฃo usada" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Impressรฃo digital prรณpria" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connect" msgstr "Serรก gerada na primeira conexรฃo" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Criptografia" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d dispositivo OMEMO" msgstr[1] "%d dispositivos OMEMO" dino-0.1.0/plugins/omemo/po/ro.po0000644000000000000000000002163213614354364015347 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2019-11-15 07:04+0000\n" "Language-Team: Romanian \n" "Language: ro\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < " "20)) ? 1 : 2;\n" "X-Generator: Weblate 3.10-dev\n" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Gestionare" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Acest contact are dispozitive noi" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "Este necesara luarea unei decizii รฎn privinศ›a OMEMO" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Aศ›i adฤƒugat un nou dispozitiv pentru contul %s?" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Gestionare cheie" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contacts device." msgstr "" "Comparaศ›i amprenta, caracter cu caracter, cu amprenta afiศ™atฤƒ pe " "dispozitivul contactului." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Not matching" msgstr "Nu se potrivesc" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Matching" msgstr "Se potrivesc" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Anulare" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Confirmare" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Verificare cheie" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "highlighted accordingly in the chat window." msgstr "" "Odatฤƒ confirmatฤƒ, orice viitoare mesaje trimise de %s folosind aceastฤƒ cheie " "vor fi evidenลฃiate รฎn consecinศ›ฤƒ รฎn fereastra de discuศ›ie." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Amprentele nu se potrivesc" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Vฤƒ rugรคm sฤƒ verificaศ›i dacฤƒ se comparฤƒ amprentele corecte. Dacฤƒ nu se " "potrivesc amprentele s-ar putea ca %s sฤƒ aibรค un cont compromis ศ™i ar trebui " "sฤƒ luaศ›i รฎn considerare respingerea acestei chei." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Verificare amprentฤƒ cheie" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Comparaศ›i amprenta cheii cu amprenta afiศ™atฤƒ pe dispozitivul contactului." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Respingere cheie" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Stop accepting this key during communication with its associated contact." msgstr "" "Nu mai accepta aceastฤƒ cheie รฎn timpul comunicรคrii cu acest contact asociat." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Acceptare cheie" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Start accepting this key during communication with its associated contact" msgstr "" "Se va accepta aceastฤƒ cheie รฎn timpul comunicฤƒrii cu acest contact asociat" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Cheia este รฎn prezent %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "acceptatฤƒ" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send messages." msgstr "" "Aceasta รฎnseamnฤƒ cฤƒ poate fi folositรค de %s sฤƒ primeascฤƒ ศ™i sฤƒ trimitฤƒ " "mesaje." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "verificatฤƒ" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "รŽn plus, a fost verificatฤƒ cฤƒ se potriveศ™te cu cheia de pe dispozitivul " "contactului." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "respinsฤƒ" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to receive messages, and any messages " "sent by it will be ignored." msgstr "" "Aceasta รฎnseamnฤƒ cฤƒ nu poate fi folositฤƒ de %s ca sฤƒ primeascฤƒ mesaje, iar " "orice mesaj trimis cu ea v-a fi ignorat." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "ignored and none of your messages will be readable using this key." msgstr "" "Odatฤƒ confirmatฤƒ, orice viitoare mesaje trimise de %s folosind aceastฤƒ cheie " "vor fi ignorate ศ™i nici un mesaj trimis de dumneavoastrฤƒ nu se va putea " "citii folosid aceastรค cheie." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "Once confirmed this key will be usable by %s to receive and send messages." msgstr "" "Odatฤƒ confirmatฤƒ, aceastรค cheie va fi utilizatรค de %s pentru a primi ศ™i " "trimite mesaje." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "รŽnapoi" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "Administrare chei OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Automat acceptฤƒ chei noi" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "" "When this contact adds new encryption keys to their account, automatically " "accept them." msgstr "" "Cรขnd acest contact adรคugฤƒ chei de criptare noi รฎn contul lor, acestea vor fi " "acceptate รฎn mod automat." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Cheie proprie" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Chei noi" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Chei asociate" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "Chei inactive" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "When you add new encryption keys to your account, automatically accept them." msgstr "" "Cรขnd adรคugaศ›i chei de criptare noi pentru acest cont, acestea vor fi " "acceptate รฎn mod automat." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Acceptatฤƒ" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Respinsฤƒ" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Verificatฤƒ" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Neutilizatรค" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Amprentฤƒ proprie" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connect" msgstr "Se va genera la prima conectare" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Criptare" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d dispozitiv OMEMO" msgstr[1] "%d dispozitive OMEMO" msgstr[2] "%d de dispozitive OMEMO" #~ msgid "" #~ "Start accepting this key during communication with its associated contact." #~ msgstr "" #~ "Acceptฤƒ aceastฤƒ cheie รฎn timpul comunicฤƒrii cu acest contact asociat." #~ msgid "Reject Key" #~ msgstr "Respingere cheie" #~ msgid "Accept Key" #~ msgstr "Acceptare cheie" #~ msgid "Unknown device (0x%.8x)" #~ msgstr "Dispozitiv necunoscut (0x%.8x)" #~ msgid "Other devices" #~ msgstr "Alte dispozitive" #~ msgid "- None -" #~ msgstr "- Nici unul -" dino-0.1.0/plugins/omemo/po/ru.po0000644000000000000000000002344013614354364015354 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2019-08-01 05:12+0000\n" "Language-Team: Russian \n" "Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "X-Generator: Weblate 3.8-dev\n" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "ะะฐัั‚ั€ะพะธั‚ัŒ" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "ะญั‚ะพั‚ ะบะพะฝั‚ะฐะบั‚ ะฒะพัะฟะพะปัŒะทะพะฒะฐะปัั ะฝะพะฒั‹ะผ ัƒัั‚ั€ะพะนัั‚ะฒะพะผ" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "ะขั€ะตะฑัƒะตั‚ัั ั€ะตัˆะตะฝะธะต OMEMO ะพ ะดะพะฒะตั€ะธะธ" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "ะ’ั‹ ะดะพะฑะฐะฒะปัะปะธ ะฐะบะบะฐัƒะฝั‚ %s ะฝะฐ ะฝะพะฒะพะต ัƒัั‚ั€ะพะนัั‚ะฒะพ?" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "ะฃะฟั€ะฐะฒะปะตะฝะธะต ะบะปัŽั‡ะพะผ" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contacts device." msgstr "" "ะกั€ะฐะฒะฝะธั‚ะต ะพั‚ะฟะตั‡ะฐั‚ะพะบ, ัะธะผะฒะพะป ะทะฐ ัะธะผะฒะพะปะพะผ, ั ั‚ะตะผ, ะบะพั‚ะพั€ั‹ะน ะฟะพะบะฐะทะฐะฝ ะฝะฐ ัƒัั‚ั€ะพะนัั‚ะฒะต " "ะฒะฐัˆะตะณะพ ะบะพะฝั‚ะฐะบั‚ะฐ." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Not matching" msgstr "ะะต ัะพะฒะฟะฐะดะฐะตั‚" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Matching" msgstr "ะกะพะฒะฟะฐะดะฐะตั‚" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "ะžั‚ะผะตะฝะฐ" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "ะŸะพะดั‚ะฒะตั€ะดะธั‚ัŒ" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "ะŸะพะดั‚ะฒะตั€ะดะธั‚ัŒ ะบะปัŽั‡" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "highlighted accordingly in the chat window." msgstr "" "ะŸะพัะปะต ะฟะพะดั‚ะฒะตั€ะถะดะตะฝะธั ะฒัะต ะฟะพัะปะตะดัƒัŽั‰ะธะต ัะพะพะฑั‰ะตะฝะธั, ะพั‚ะฟั€ะฐะฒะปะตะฝะฝั‹ะต ั %s ะฟั€ะธ ะฟะพะผะพั‰ะธ " "ัั‚ะพะณะพ ะบะปัŽั‡ะฐ, ะฑัƒะดัƒั‚ ัะพะพั‚ะฒะตั‚ัั‚ะฒะตะฝะฝะพ ะฟะพะดัะฒะตั‡ะตะฝั‹ ะฒ ะพะบะฝะต ั‡ะฐั‚ะฐ." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "ะžั‚ะฟะตั‡ะฐั‚ะบะธ ะฝะต ัะพะฒะฟะฐะดะฐัŽั‚" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "ะŸะพะถะฐะปัƒะนัั‚ะฐ, ะฟั€ะพะฒะตั€ัŒั‚ะต ัะพะฒะฟะฐะดะตะฝะธะต ะพั‚ะฟะตั‡ะฐั‚ะบะพะฒ. ะ•ัะปะธ ะพะฝะธ ะฝะต ัะพะฒะฟะฐะดะฐัŽั‚, ะฐะบะบะฐัƒะฝั‚ " "\"%s\" ะผะพะถะตั‚ ะฑั‹ั‚ัŒ ัะบะพะผะฟั€ะพะผะตั‚ะธั€ะพะฒะฐะฝ, ะธ ะฒะฐะผ ัั‚ะพะธั‚ ะพั‚ะบะปะพะฝะธั‚ัŒ ัั‚ะพั‚ ะบะปัŽั‡." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "ะŸะพะดั‚ะฒะตั€ะดะธั‚ัŒ ะพั‚ะฟะตั‡ะฐั‚ะพะบ" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "ะกะฒะตั€ัŒั‚ะต ัั‚ะพั‚ ะพั‚ะฟะตั‡ะฐั‚ะพะบ ั ะบะปัŽั‡ะพะผ, ะพั‚ะพะฑั€ะฐะถะฐะตะผั‹ะผ ัƒ ะบะพะฝั‚ะฐะบั‚ะฐ." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "ะžั‚ะบะปะพะฝะธั‚ัŒ ะบะปัŽั‡" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Stop accepting this key during communication with its associated contact." msgstr "ะะต ะฟั€ะธะฝะธะผะฐั‚ัŒ ะบะปัŽั‡ ัั‚ะพะณะพ ะบะพะฝั‚ะฐะบั‚ะฐ." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "ะŸั€ะธะฝัั‚ัŒ ะบะปัŽั‡" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Start accepting this key during communication with its associated contact" msgstr "ะŸั€ะธะฝัั‚ัŒ ะบะปัŽั‡ ัั‚ะพะณะพ ะบะพะฝั‚ะฐะบั‚ะฐ" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "ะšะปัŽั‡ ะฑั‹ะป ัƒัะฟะตัˆะฝะพ %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "ะฟั€ะธะฝัั‚" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send messages." msgstr "ะญั‚ะพ ะพะทะฝะฐั‡ะฐะตั‚, ั‡ั‚ะพ %s ั‚ะตะฟะตั€ัŒ ะผะพะถะตั‚ ะพั‚ะฟั€ะฐะฒะปัั‚ัŒ ะธ ะฟั€ะธะฝะธะผะฐั‚ัŒ ัะพะพะฑั‰ะตะฝะธั." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "ะฟั€ะพะฒะตั€ะตะฝ" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "ะขะฐะบะถะต ะฑั‹ะปะฐ ะฟั€ะพะฒะตะดะตะฝะฐ ะฟั€ะพะฒะตั€ะบะฐ ัะพะฒะฟะฐะดะตะฝะธั ะบะปัŽั‡ะฐ." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "ะพั‚ะบะปะพะฝั‘ะฝ" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to receive messages, and any messages " "sent by it will be ignored." msgstr "" "ะญั‚ะพ ะทะฝะฐั‡ะธั‚, ั‡ั‚ะพ %s ะฝะต ะผะพะถะตั‚ ะพั‚ะฟั€ะฐะฒะปัั‚ัŒ ัะพะพะฑั‰ะตะฝะธัย โ€” ะฒัะต ะพะฝะธ ะฑัƒะดัƒั‚ " "ะธะณะฝะพั€ะธั€ะพะฒะฐั‚ัŒัั." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "ignored and none of your messages will be readable using this key." msgstr "" "ะŸะพัะปะต ะฟะพะดั‚ะฒะตั€ะถะดะตะฝะธั ะฒัะต ะฑัƒะดัƒั‰ะธะต ัะพะพะฑั‰ะตะฝะธั, ะพั‚ะฟั€ะฐะฒะปะตะฝะฝั‹ะต %s ั ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะตะผ " "ัั‚ะพะณะพ ะบะปัŽั‡ะฐ, ะฑัƒะดัƒั‚ ะธะณะฝะพั€ะธั€ะพะฒะฐั‚ัŒัั ะธ ะฝะธ ะพะดะฝะพ ะธะท ะฒะฐัˆะธั… ัะพะพะฑั‰ะตะฝะธะน ะฝะต ัะผะพะถะตั‚ " "ะฑั‹ั‚ัŒ ะฟั€ะพั‡ะธั‚ะฐะฝะพ ั ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะตะผ ัั‚ะพะณะพ ะบะปัŽั‡ะฐ." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "Once confirmed this key will be usable by %s to receive and send messages." msgstr "" "ะŸะพัะปะต ะฟะพะดั‚ะฒะตั€ะถะดะตะฝะธั ัั‚ะพั‚ ะบะปัŽั‡ ะฑัƒะดะตั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒัั %s ะดะปั ะฟะพะปัƒั‡ะตะฝะธั ะธ " "ะพั‚ะฟั€ะฐะฒะบะธ ัะพะพะฑั‰ะตะฝะธะน." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "ะะฐะทะฐะด" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "ะฃะฟั€ะฐะฒะปะตะฝะธะต ะบะปัŽั‡ะฐะผะธ OMEMO" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ะฟั€ะธะฝะธะผะฐั‚ัŒ ะฝะพะฒั‹ะต ะบะปัŽั‡ะธ" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "" "When this contact adds new encryption keys to their account, automatically " "accept them." msgstr "" "ะšะพะณะดะฐ ัั‚ะพั‚ ะบะพะฝั‚ะฐะบั‚ ะดะพะฑะฐะฒะปัะตั‚ ะฝะพะฒั‹ะต ะบะปัŽั‡ะธ ัˆะธั„ั€ะพะฒะฐะฝะธั ะฒ ัะฒะพัŽ ัƒั‡ะตั‚ะฝัƒัŽ ะทะฐะฟะธััŒ, " "ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ะฟั€ะธะฝะธะผะฐะนั‚ะต ะธั…." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "ะกะพะฑัั‚ะฒะตะฝะฝั‹ะน ะบะปัŽั‡" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "ะะพะฒั‹ะต ะบะปัŽั‡ะธ" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "ะกะฒัะทะฐะฝะฝั‹ะต ะบะปัŽั‡ะธ" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "When you add new encryption keys to your account, automatically accept them." msgstr "" "ะšะพะณะดะฐ ะฒั‹ ะดะพะฑะฐะฒะปัะตั‚ะต ะฝะพะฒั‹ะต ะบะปัŽั‡ะธ ัˆะธั„ั€ะพะฒะฐะฝะธั ะฒ ัะฒะพัŽ ัƒั‡ะตั‚ะฝัƒัŽ ะทะฐะฟะธััŒ, " "ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ะฟั€ะธะฝะธะผะฐะนั‚ะต ะธั…." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "ะŸั€ะธะฝัั‚" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "ะžั‚ะบะปะพะฝั‘ะฝ" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "ะŸะพะดั‚ะฒะตั€ะถะดั‘ะฝ" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "ะะต ะธัะฟะพะปัŒะทัƒะตั‚ัั" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "ะžั‚ะฟะตั‡ะฐั‚ะพะบ ัั‚ะพะณะพ ัƒัั‚ั€ะพะนัั‚ะฒะฐ" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connect" msgstr "ะ‘ัƒะดะตั‚ ัะณะตะฝะตั€ะธั€ะพะฒะฐะฝ ะฟั€ะธ ะฟะตั€ะฒะพะผ ะฟะพะดะบะปัŽั‡ะตะฝะธะธ" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "ะจะธั„ั€ะพะฒะฐะฝะธะต" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d ัƒัั‚ั€ะพะนัั‚ะฒะพ OMEMO" msgstr[1] "%d ัƒัั‚ั€ะพะนัั‚ะฒะฐ OMEMO" msgstr[2] "%d ัƒัั‚ั€ะพะนัั‚ะฒ OMEMO" #~ msgid "Unknown device (0x%.8x)" #~ msgstr "ะะตะธะทะฒะตัั‚ะฝะพะต ัƒัั‚ั€ะพะนัั‚ะฒะพ (0x%.8x)" #~ msgid "Other devices" #~ msgstr "ะ”ั€ัƒะณะธะต ัƒัั‚ั€ะพะนัั‚ะฒะฐ" #~ msgid "- None -" #~ msgstr "- ะะตั‚ -" dino-0.1.0/plugins/omemo/po/sv.po0000644000000000000000000002014713614354364015357 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2019-08-14 11:23+0000\n" "Language-Team: none\n" "Language: sv\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.8-dev\n" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "Hantera" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "Denna kontakt har nya enheter" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "OMEMO-tillitsbeslut krรคvs" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "Har du lagt till en ny enhet fรถr kontot %s?" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "Hantera nyckel" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contacts device." msgstr "" "Jรคmfรถr fingeravtrycket, tecken fรถr tecken, med det som visas i din kontakts " "enhet." #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Not matching" msgstr "Stรคmmer ej" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Matching" msgstr "Stรคmmer" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "Avbryt" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "Bekrรคfta" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "Verifiera nyckel" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "highlighted accordingly in the chat window." msgstr "" "Om du gรฅr vidare sรฅ kommer framtida meddelanden frรฅn %s med denna nyckel att " "markeras i enlighet med nyckelns nya status." #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "Fingeravtrycken รถverensstรคmmer inte" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "Kontrollera att du jรคmfรถr rรคtt fingeravtryck. Om fingeravtrycken inte " "stรคmmer sรฅ kan %s konto vara underminerat och du bรถr รถvervรคga att avvisa " "denna nyckel." #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "Verifiera nyckelns fingeravtryck" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" "Jรคmfรถr nyckelns fingeravtryck med det fingeravtryck som visas i kontaktens " "enhet." #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "Avvisa nyckeln" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Stop accepting this key during communication with its associated contact." msgstr "" "Sluta acceptera denna nyckel vid kommunikation med den tillhรถrande kontakten." #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "Acceptera nyckeln" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Start accepting this key during communication with its associated contact" msgstr "" "Bรถrja acceptera nyckeln fรถr kommunikation med den tillhรถrande kontakten" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "Denna nyckel รคr fรถr nรคrvarande %s." #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "accepterad" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send messages." msgstr "" "Det betyder att den kan anvรคndas av %s fรถr att skicka och ta emot " "meddelanden." #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "verifierad" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" "Dessutom har den verifierats sรฅ att den รถverensstรคmmer med nyckeln pรฅ " "kontaktens enhet." #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "avvisad" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to receive messages, and any messages " "sent by it will be ignored." msgstr "" "Det betyder att den inte kan anvรคndas av %s fรถr att ta emot meddelanden, och " "att alla skickade meddelanden kommer att ignoreras." #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "ignored and none of your messages will be readable using this key." msgstr "" "Om du gรฅr vidare sรฅ kommer framtida meddelanden frรฅn %s som anvรคnder denna " "nyckel att ignoreras, och meddelanden frรฅn dig blir olรคsbara med denna " "nyckel." #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "Once confirmed this key will be usable by %s to receive and send messages." msgstr "" "Om du gรฅr vidare sรฅ kommer nyckeln kunna anvรคndas av %s fรถr att skicka och " "ta emot meddelanden." #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "Tillbaka" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "OMEMO-nyckelhantering" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "Acceptera automatiskt nya nycklar" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "" "When this contact adds new encryption keys to their account, automatically " "accept them." msgstr "Acceptera automatiskt nya nycklar som lรคggs till av denna kontakt." #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "Egen nyckel" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "Ny nyckel" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "Kopplade nycklar" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "When you add new encryption keys to your account, automatically accept them." msgstr "Acceptera automatiskt nya nycklar som du lรคggs till ditt konto." #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "Accepterad" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "Avvisad" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "Verifierad" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "Oanvรคnd" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "Eget fingeravtryck" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connect" msgstr "Genereras vid fรถrsta anslutningstillfรคllet" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "Kryptering" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d OMEMO-enhet" msgstr[1] "%d OMEMO-enheter" dino-0.1.0/plugins/omemo/po/zh_CN.po0000644000000000000000000001741713614354364015736 0ustar rootrootmsgid "" msgstr "" "Project-Id-Version: dino-omemo-0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2019-02-02 14:09+0000\n" "Language-Team: Chinese (Simplified) \n" "Language: zh_Hans\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 3.5-dev\n" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "็ฎก็†" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "ๆญค่”็ณปไบบๆœ‰ๆ–ฐ่ฎพๅค‡" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "้œ€่ฆOmemoไฟกไปปๅ†ณ็ญ–" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "ๆ˜ฏๅฆไธบๅธๆˆท%sๆทปๅŠ ไบ†ๆ–ฐ่ฎพๅค‡๏ผŸ" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "็ฎก็†ๅฏ†้’ฅ" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contacts device." msgstr "ๅฐ†ๆŒ‡็บนไธŽ่”็ณปไบบ่ฎพๅค‡ไธŠๆ˜พ็คบ็š„ๆŒ‡็บนๆŒ‰้€ไธชๅญ—็ฌฆ่ฟ›่กŒๆฏ”่พƒใ€‚" #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Not matching" msgstr "ไธๅŒน้…" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Matching" msgstr "ๅŒน้…" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "ๅ–ๆถˆ" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "็กฎ่ฎค" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "้ชŒ่ฏๅฏ†้’ฅ" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "highlighted accordingly in the chat window." msgstr "ไธ€ๆ—ฆ็กฎ่ฎค๏ผŒๆœชๆฅ็”ฑ%sไฝฟ็”จๆญคๅฏ†้’ฅๅ‘้€็š„ไปปไฝ•ๆถˆๆฏๅฐ†ๅœจ่Šๅคฉ็ช—ๅฃไธญ็›ธๅบ”็ชๅ‡บๆ˜พ็คบใ€‚" #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "ๆŒ‡็บนไธๅŒน้…" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" "่ฏท็กฎ่ฎคๆ‚จๆญฃๅœจๆฏ”่พƒๆญฃ็กฎ็š„ๆŒ‡็บนใ€‚ๅฆ‚ๆžœๆŒ‡็บนไธๅŒน้…๏ผŒๅฏ่ƒฝไผš็ ดๅ%s็š„ๅธๆˆท๏ผŒๆ‚จๅบ”่€ƒ่™‘ๆ‹’็ป" "ไฝฟ็”จๆญคๅฏ†้’ฅใ€‚" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "้ชŒ่ฏๅฏ†้’ฅๆŒ‡็บน" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "ๅฐ†ๆญคๅฏ†้’ฅ็š„ๆŒ‡็บนไธŽ่”็ณปไบบ่ฎพๅค‡ไธŠๆ˜พ็คบ็š„ๆŒ‡็บน่ฟ›่กŒๆฏ”่พƒใ€‚" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "ๆ‹’็ปๅฏ†้’ฅ" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Stop accepting this key during communication with its associated contact." msgstr "ๅœจไธŽๅ…ถๅ…ณ่”็š„่”็ณปไบบ้€šไฟกๆœŸ้—ดๅœๆญขๆŽฅๅ—ๆญคๅฏ†้’ฅใ€‚" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "ๆŽฅๅ—ๅฏ†้’ฅ" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Start accepting this key during communication with its associated contact" msgstr "ๅœจไธŽๅ…ถๅ…ณ่”็š„่”็ณปไบบ้€šไฟกๆœŸ้—ดๅผ€ๅง‹ๆŽฅๅ—ๆญคๅฏ†้’ฅ" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "ๆญคๅฏ†้’ฅๅฝ“ๅ‰ไธบ%sใ€‚" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "ๆŽฅๅ—" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send messages." msgstr "่ฟ™ๆ„ๅ‘ณ็€%s ๅฏไปฅไฝฟ็”จๅฎƒๆฅๆŽฅๆ”ถๅ’Œๅ‘้€ๆถˆๆฏใ€‚" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "้ชŒ่ฏ" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "ๆญคๅค–๏ผŒๅทฒ้ชŒ่ฏๅฎƒไธŽ่”็ณปไบบ่ฎพๅค‡ไธŠ็š„ๅฏ†้’ฅๅŒน้…ใ€‚" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "ๆ‹’็ป" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to receive messages, and any messages " "sent by it will be ignored." msgstr "่ฟ™ๆ„ๅ‘ณ็€%sไธ่ƒฝไฝฟ็”จๅฎƒๆฅๆŽฅๆ”ถๆถˆๆฏ, ๅฎƒๅ‘้€็š„ไปปไฝ•ๆถˆๆฏ้ƒฝๅฐ†่ขซๅฟฝ็•ฅใ€‚" #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "ignored and none of your messages will be readable using this key." msgstr "" "ไธ€ๆ—ฆ็กฎ่ฎค๏ผŒ็”ฑ%sไฝฟ็”จๆญคๅฏ†้’ฅๅ‘้€็š„ไปปไฝ•ๆœชๆฅๆถˆๆฏ้ƒฝๅฐ†่ขซๅฟฝ็•ฅ๏ผŒๅนถไธ”ไฝฟ็”จๆญคๅฏ†้’ฅๅฐ†ๆ— ๆณ•่ฏป" "ๅ–ไปปไฝ•ๆถˆๆฏใ€‚" #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "Once confirmed this key will be usable by %s to receive and send messages." msgstr "ไธ€ๆ—ฆ็กฎ่ฎค๏ผŒๆญคๅฏ†้’ฅๅฐ†่ขซ%s็”จไบŽๆŽฅๆ”ถๅ’Œๅ‘้€ๆถˆๆฏใ€‚" #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "่ฟ”ๅ›ž" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "OMEMOใ€€ๅฏ†้’ฅ็ฎก็†" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "่‡ชๅŠจๆŽฅๅ—ๆ–ฐๅฏ†้’ฅ" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "" "When this contact adds new encryption keys to their account, automatically " "accept them." msgstr "ๅฝ“ๆญค่”็ณปไบบๅ‘ๅ…ถๅธๆˆทๆทปๅŠ ๆ–ฐ็š„ๅŠ ๅฏ†ๅฏ†้’ฅๆ—ถ๏ผŒ่‡ชๅŠจๆŽฅๅ—ๅฎƒไปฌใ€‚" #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "่‡ชๅทฑ็š„้’ฅๅŒ™" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "ๆ–ฐๅฏ†้’ฅ" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "ๅ…ณ่”ๅฏ†้’ฅ" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "When you add new encryption keys to your account, automatically accept them." msgstr "ๅฝ“ๆ‚จๅ‘ๅธๆˆทๆทปๅŠ ๆ–ฐ็š„ๅŠ ๅฏ†ๅฏ†้’ฅๆ—ถ๏ผŒไผš่‡ชๅŠจๆŽฅๅ—ๅฎƒไปฌใ€‚" #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "ๆŽฅๅ—็š„" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "ๆ‹’็ป็š„" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "้ชŒ่ฏ็š„" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "ๆœชไฝฟ็”จ็š„" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "่‡ชๅทฑ็š„ๆŒ‡็บน" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connect" msgstr "ๅฐ†ๅœจ็ฌฌไธ€ๆฌก่ฟžๆŽฅๆ—ถ็”Ÿๆˆ" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "ๅŠ ๅฏ†" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "%d ไธช OMEMO ่ฎพๅค‡" #~ msgid "Unknown device (0x%.8x)" #~ msgstr "ๆœช็Ÿฅ่ฎพๅค‡ (0x%.8x)" #~ msgid "Other devices" #~ msgstr "ๅ…ถไป–่ฎพๅค‡" #~ msgid "- None -" #~ msgstr "- ๆ—  -" #~ msgid "Database error" #~ msgstr "ๆ•ฐๆฎๅบ“้”™่ฏฏ" dino-0.1.0/plugins/omemo/po/zh_TW.po0000644000000000000000000001462313614354364015764 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2019-05-11 09:48+0000\n" "Language-Team: Chinese (Traditional) \n" "Language: zh_Hant\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 3.7-dev\n" #: plugins/omemo/src/ui/device_notification_populator.vala:73 msgid "Manage" msgstr "็ฎก็†" #: plugins/omemo/src/ui/device_notification_populator.vala:83 msgid "This contact has new devices" msgstr "ๆญค้€ฃ็ตกไบบๆœ‰ๆ–ฐ็š„่ฃ็ฝฎ" #: plugins/omemo/src/ui/own_notifications.vala:29 msgid "OMEMO trust decision required" msgstr "้œ€่ฆ OMEMO ไฟกไปปๆฑบ็ญ–" #: plugins/omemo/src/ui/own_notifications.vala:31 #, c-format msgid "Did you add a new device for account %s?" msgstr "ๆ˜ฏๅฆ็‚บๅธณๆˆถ %s ๆทปๅŠ ๆ–ฐ่จญๅ‚™๏ผŸ" #: plugins/omemo/src/ui/manage_key_dialog.vala:35 msgid "Manage Key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:36 msgid "" "Compare the fingerprint, character by character, with the one shown on your " "contacts device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:37 msgid "Not matching" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:38 msgid "Matching" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:39 #: plugins/omemo/src/ui/manage_key_dialog.vala:82 #: plugins/omemo/src/ui/manage_key_dialog.vala:88 msgid "Cancel" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:40 msgid "Confirm" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:60 msgid "Verify key" msgstr "้ฉ—่ญ‰้‡‘้‘ฐ" #: plugins/omemo/src/ui/manage_key_dialog.vala:61 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "highlighted accordingly in the chat window." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:71 msgid "Fingerprints do not match" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:72 #, c-format msgid "" "Please verify that you are comparing the correct fingerprint. If " "fingerprints do not match, %s's account may be compromised and you should " "consider rejecting this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "Verify key fingerprint" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:124 msgid "" "Compare this key's fingerprint with the fingerprint displayed on the " "contact's device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 #: plugins/omemo/src/ui/manage_key_dialog.vala:152 #: plugins/omemo/src/ui/contact_details_dialog.vala:256 msgid "Reject key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:126 msgid "" "Stop accepting this key during communication with its associated contact." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 #: plugins/omemo/src/ui/manage_key_dialog.vala:160 #: plugins/omemo/src/ui/contact_details_dialog.vala:251 msgid "Accept key" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:128 msgid "" "Start accepting this key during communication with its associated contact" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "This key is currently %s." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 msgid "accepted" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:132 #: plugins/omemo/src/ui/manage_key_dialog.vala:137 #, c-format msgid "This means it can be used by %s to receive and send messages." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "verified" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:137 msgid "" "Additionally it has been verified to match the key on the contact's device." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 msgid "rejected" msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:141 #, c-format msgid "" "This means it cannot be used by %s to receive messages, and any messages " "sent by it will be ignored." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:153 #, c-format msgid "" "Once confirmed, any future messages sent by %s using this key will be " "ignored and none of your messages will be readable using this key." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:161 #, c-format msgid "" "Once confirmed this key will be usable by %s to receive and send messages." msgstr "" #: plugins/omemo/src/ui/manage_key_dialog.vala:167 msgid "Back" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:46 msgid "OMEMO Key Management" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:47 msgid "Automatically accept new keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:48 msgid "" "When this contact adds new encryption keys to their account, automatically " "accept them." msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:49 msgid "Own key" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:50 msgid "New keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:51 msgid "Associated keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:52 msgid "Inactive keys" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:84 msgid "" "When you add new encryption keys to your account, automatically accept them." msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:319 msgid "Accepted" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:324 msgid "Rejected" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:329 msgid "Verified" msgstr "" #: plugins/omemo/src/ui/contact_details_dialog.vala:336 msgid "Unused" msgstr "" #: plugins/omemo/src/ui/account_settings_widget.vala:42 #: plugins/omemo/src/ui/account_settings_widget.vala:45 msgid "Own fingerprint" msgstr "" #: plugins/omemo/src/ui/account_settings_widget.vala:42 msgid "Will be generated on first connect" msgstr "" #: plugins/omemo/src/ui/contact_details_provider.vala:42 msgid "Encryption" msgstr "ๅŠ ๅฏ†" #: plugins/omemo/src/ui/contact_details_provider.vala:42 #, c-format msgid "%d OMEMO device" msgid_plural "%d OMEMO devices" msgstr[0] "" dino-0.1.0/plugins/omemo/src/0000755000000000000000000000000013614354364014534 5ustar rootrootdino-0.1.0/plugins/omemo/src/file_transfer/0000755000000000000000000000000013614354364017357 5ustar rootrootdino-0.1.0/plugins/omemo/src/file_transfer/file_decryptor.vala0000644000000000000000000000730013614354364023236 0ustar rootrootusing Dino.Entities; using Crypto; using Signal; namespace Dino.Plugins.Omemo { public class OmemoHttpFileReceiveData : HttpFileReceiveData { public string original_url; } public class OmemoFileDecryptor : FileDecryptor, Object { private Regex url_regex = /^aesgcm:\/\/(.*)#(([A-Fa-f0-9]{2}){48}|([A-Fa-f0-9]{2}){44})$/; public FileReceiveData prepare_get_meta_info(Conversation conversation, FileTransfer file_transfer, FileReceiveData receive_data) { HttpFileReceiveData? http_receive_data = receive_data as HttpFileReceiveData; if (http_receive_data == null) assert(false); if ((receive_data as OmemoHttpFileReceiveData) != null) return receive_data; var omemo_http_receive_data = new OmemoHttpFileReceiveData(); omemo_http_receive_data.url = aesgcm_to_https_link(http_receive_data.url); omemo_http_receive_data.original_url = http_receive_data.url; return omemo_http_receive_data; } public FileMeta prepare_download_file(Conversation conversation, FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta) { if (file_meta.file_name != null) { file_meta.file_name = file_meta.file_name.split("#")[0]; } return file_meta; } public bool can_decrypt_file(Conversation conversation, FileTransfer file_transfer, FileReceiveData receive_data) { HttpFileReceiveData? http_file_receive = receive_data as HttpFileReceiveData; if (http_file_receive == null) return false; return this.url_regex.match(http_file_receive.url) || (receive_data as OmemoHttpFileReceiveData) != null; } public async InputStream decrypt_file(InputStream encrypted_stream, Conversation conversation, FileTransfer file_transfer, FileReceiveData receive_data) throws FileReceiveError { try { OmemoHttpFileReceiveData? omemo_http_receive_data = receive_data as OmemoHttpFileReceiveData; if (omemo_http_receive_data == null) assert(false); // Decode IV and key MatchInfo match_info; this.url_regex.match(omemo_http_receive_data.original_url, 0, out match_info); uint8[] iv_and_key = hex_to_bin(match_info.fetch(2).up()); uint8[] iv, key; if (iv_and_key.length == 44) { iv = iv_and_key[0:12]; key = iv_and_key[12:44]; } else { iv = iv_and_key[0:16]; key = iv_and_key[16:48]; } file_transfer.encryption = Encryption.OMEMO; debug("Decrypting file %s from %s", file_transfer.file_name, file_transfer.server_file_name); SymmetricCipher cipher = new SymmetricCipher("AES-GCM"); cipher.set_key(key); cipher.set_iv(iv); return new ConverterInputStream(encrypted_stream, new SymmetricCipherDecrypter((owned) cipher, 16)); } catch (Crypto.Error e) { throw new FileReceiveError.DECRYPTION_FAILED("OMEMO file decryption error: %s".printf(e.message)); } catch (GLib.Error e) { throw new FileReceiveError.DECRYPTION_FAILED("OMEMO file decryption error: %s".printf(e.message)); } } private uint8[] hex_to_bin(string hex) { uint8[] bin = new uint8[hex.length / 2]; const string HEX = "0123456789ABCDEF"; for (int i = 0; i < hex.length / 2; i++) { bin[i] = (uint8) (HEX.index_of_char(hex[i*2]) << 4) | HEX.index_of_char(hex[i*2+1]); } return bin; } private string aesgcm_to_https_link(string aesgcm_link) { MatchInfo match_info; this.url_regex.match(aesgcm_link, 0, out match_info); return "https://" + match_info.fetch(1); } } } dino-0.1.0/plugins/omemo/src/file_transfer/file_encryptor.vala0000644000000000000000000000530713614354364023255 0ustar rootrootusing Gee; using Gtk; using Crypto; using Dino.Entities; using Xmpp; using Signal; namespace Dino.Plugins.Omemo { public class OmemoHttpFileMeta : HttpFileMeta { public uint8[] iv; public uint8[] key; } public class OmemoFileEncryptor : Dino.FileEncryptor, Object { public bool can_encrypt_file(Conversation conversation, FileTransfer file_transfer) { return file_transfer.encryption == Encryption.OMEMO; } public FileMeta encrypt_file(Conversation conversation, FileTransfer file_transfer) throws FileSendError { var omemo_http_file_meta = new OmemoHttpFileMeta(); try { //Create a key and use it to encrypt the file uint8[] iv = new uint8[16]; Plugin.get_context().randomize(iv); uint8[] key = new uint8[32]; Plugin.get_context().randomize(key); SymmetricCipher cipher = new SymmetricCipher("AES-GCM"); cipher.set_key(key); cipher.set_iv(iv); omemo_http_file_meta.iv = iv; omemo_http_file_meta.key = key; omemo_http_file_meta.size = file_transfer.size + 16; omemo_http_file_meta.mime_type = "omemo"; file_transfer.input_stream = new ConverterInputStream(file_transfer.input_stream, new SymmetricCipherEncrypter((owned) cipher, 16)); } catch (Crypto.Error error) { throw new FileSendError.ENCRYPTION_FAILED("OMEMO file encryption error: %s".printf(error.message)); } catch (GLib.Error error) { throw new FileSendError.ENCRYPTION_FAILED("OMEMO file encryption error: %s".printf(error.message)); } debug("Encrypting file %s as %s", file_transfer.file_name, file_transfer.server_file_name); return omemo_http_file_meta; } public FileSendData? preprocess_send_file(Conversation conversation, FileTransfer file_transfer, FileSendData file_send_data, FileMeta file_meta) { HttpFileSendData? send_data = file_send_data as HttpFileSendData; if (send_data == null) return null; OmemoHttpFileMeta? omemo_http_file_meta = file_meta as OmemoHttpFileMeta; if (omemo_http_file_meta == null) return null; // Convert iv and key to hex string iv_and_key = ""; foreach (uint8 byte in omemo_http_file_meta.iv) iv_and_key += byte.to_string("%02x"); foreach (uint8 byte in omemo_http_file_meta.key) iv_and_key += byte.to_string("%02x"); string aesgcm_link = send_data.url_down + "#" + iv_and_key; aesgcm_link = "aesgcm://" + aesgcm_link.substring(8); // replace https:// by aesgcm:// send_data.url_down = aesgcm_link; send_data.encrypt_message = true; return file_send_data; } } } dino-0.1.0/plugins/omemo/src/jingle/0000755000000000000000000000000013614354364016004 5ustar rootrootdino-0.1.0/plugins/omemo/src/jingle/jet_omemo.vala0000644000000000000000000001557313614354364020642 0ustar rootrootusing Crypto; using Dino; using Dino.Entities; using Gee; using Signal; using Xmpp; using Xmpp.Xep; namespace Dino.Plugins.JetOmemo { private const string NS_URI = "urn:xmpp:jingle:jet-omemo:0"; private const string AES_128_GCM_URI = "urn:xmpp:ciphers:aes-128-gcm-nopadding"; public class Module : XmppStreamModule, Jet.EnvelopEncoding { public static Xmpp.ModuleIdentity IDENTITY = new Xmpp.ModuleIdentity(NS_URI, "0396_jet_omemo"); private Omemo.Plugin plugin; public Module(Omemo.Plugin plugin) { this.plugin = plugin; } public override void attach(XmppStream stream) { if (stream.get_module(Jet.Module.IDENTITY) != null) { stream.get_module(ServiceDiscovery.Module.IDENTITY).add_feature(stream, NS_URI); stream.get_module(Jet.Module.IDENTITY).register_envelop_encoding(this); stream.get_module(Jet.Module.IDENTITY).register_cipher(new AesGcmCipher(16, AES_128_GCM_URI)); } } public override void detach(XmppStream stream) { } public bool is_available(XmppStream stream, Jid full_jid) { bool? has_feature = stream.get_flag(ServiceDiscovery.Flag.IDENTITY).has_entity_feature(full_jid, NS_URI); if (has_feature == null || !(!)has_feature) { return false; } return stream.get_module(Xep.Jet.Module.IDENTITY).is_available(stream, full_jid); } public string get_type_uri() { return Omemo.NS_URI; } public Jet.TransportSecret decode_envolop(XmppStream stream, Jid local_full_jid, Jid peer_full_jid, StanzaNode security) throws Jingle.IqError { Store store = stream.get_module(Omemo.StreamModule.IDENTITY).store; StanzaNode? encrypted = security.get_subnode("encrypted", Omemo.NS_URI); if (encrypted == null) throw new Jingle.IqError.BAD_REQUEST("Invalid JET-OMEMO envelop: missing encrypted element"); StanzaNode? header = encrypted.get_subnode("header", Omemo.NS_URI); if (header == null) throw new Jingle.IqError.BAD_REQUEST("Invalid JET-OMEMO envelop: missing header element"); string? iv_node = header.get_deep_string_content("iv"); if (header == null) throw new Jingle.IqError.BAD_REQUEST("Invalid JET-OMEMO envelop: missing iv element"); uint8[] iv = Base64.decode((!)iv_node); foreach (StanzaNode key_node in header.get_subnodes("key")) { if (key_node.get_attribute_int("rid") == store.local_registration_id) { string? key_node_content = key_node.get_string_content(); uint8[] key; Address address = new Address(peer_full_jid.bare_jid.to_string(), header.get_attribute_int("sid")); if (key_node.get_attribute_bool("prekey")) { PreKeySignalMessage msg = Omemo.Plugin.get_context().deserialize_pre_key_signal_message(Base64.decode((!)key_node_content)); SessionCipher cipher = store.create_session_cipher(address); key = cipher.decrypt_pre_key_signal_message(msg); } else { SignalMessage msg = Omemo.Plugin.get_context().deserialize_signal_message(Base64.decode((!)key_node_content)); SessionCipher cipher = store.create_session_cipher(address); key = cipher.decrypt_signal_message(msg); } address.device_id = 0; // TODO: Hack to have address obj live longer uint8[] authtag = null; if (key.length >= 32) { int authtaglength = key.length - 16; authtag = new uint8[authtaglength]; uint8[] new_key = new uint8[16]; Memory.copy(authtag, (uint8*)key + 16, 16); Memory.copy(new_key, key, 16); key = new_key; } // TODO: authtag? return new Jet.TransportSecret(key, iv); } } throw new Jingle.IqError.NOT_ACCEPTABLE("Not encrypted for targeted device"); } public void encode_envelop(XmppStream stream, Jid local_full_jid, Jid peer_full_jid, Jet.SecurityParameters security_params, StanzaNode security) { ArrayList accounts = plugin.app.stream_interactor.get_accounts(); Store store = stream.get_module(Omemo.StreamModule.IDENTITY).store; Account? account = null; foreach (Account compare in accounts) { if (compare.bare_jid.equals_bare(local_full_jid)) { account = compare; break; } } if (account == null) { // TODO critical("Sending from offline account %s", local_full_jid.to_string()); } StanzaNode header_node; StanzaNode encrypted_node = new StanzaNode.build("encrypted", Omemo.NS_URI).add_self_xmlns() .put_node(header_node = new StanzaNode.build("header", Omemo.NS_URI) .put_attribute("sid", store.local_registration_id.to_string()) .put_node(new StanzaNode.build("iv", Omemo.NS_URI) .put_node(new StanzaNode.text(Base64.encode(security_params.secret.initialization_vector))))); plugin.trust_manager.encrypt_key(header_node, security_params.secret.transport_key, local_full_jid.bare_jid, new ArrayList.wrap(new Jid[] {peer_full_jid.bare_jid}), stream, account); security.put_node(encrypted_node); } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } public class AesGcmCipher : Jet.Cipher, Object { private int key_size; private string uri; public AesGcmCipher(int key_size, string uri) { this.key_size = key_size; this.uri = uri; } public string get_cipher_uri() { return uri; } public Jet.TransportSecret generate_random_secret() { uint8[] iv = new uint8[16]; Omemo.Plugin.get_context().randomize(iv); uint8[] key = new uint8[key_size]; Omemo.Plugin.get_context().randomize(key); return new Jet.TransportSecret(key, iv); } public InputStream wrap_input_stream(InputStream input, Jet.TransportSecret secret) requires (secret.transport_key.length == key_size) { SymmetricCipher cipher = new SymmetricCipher("AES-GCM"); cipher.set_key(secret.transport_key); cipher.set_iv(secret.initialization_vector); return new ConverterInputStream(input, new SymmetricCipherDecrypter((owned) cipher, 16)); } public OutputStream wrap_output_stream(OutputStream output, Jet.TransportSecret secret) requires (secret.transport_key.length == key_size) { Crypto.SymmetricCipher cipher = new SymmetricCipher("AES-GCM"); cipher.set_key(secret.transport_key); cipher.set_iv(secret.initialization_vector); return new ConverterOutputStream(output, new SymmetricCipherEncrypter((owned) cipher, 16)); } } } dino-0.1.0/plugins/omemo/src/jingle/jingle_helper.vala0000644000000000000000000000366713614354364021474 0ustar rootrootusing Dino.Entities; using Xmpp; namespace Dino.Plugins.JetOmemo { public class EncryptionHelper : JingleFileEncryptionHelper, Object { private StreamInteractor stream_interactor; public EncryptionHelper(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public bool can_transfer(Conversation conversation) { return true; } public bool can_encrypt(Conversation conversation, FileTransfer file_transfer, Jid? full_jid) { XmppStream? stream = stream_interactor.get_stream(conversation.account); if (stream == null) return false; Gee.List? resources = stream.get_flag(Presence.Flag.IDENTITY).get_resources(conversation.counterpart); if (resources == null) return false; if (full_jid == null) { foreach (Jid test_jid in resources) { if (stream.get_module(Module.IDENTITY).is_available(stream, test_jid)) { return true; } } } else { if (stream.get_module(Module.IDENTITY).is_available(stream, full_jid)) { return true; } } return false; } public string? get_precondition_name(Conversation conversation, FileTransfer file_transfer) { return Xep.Jet.NS_URI; } public Object? get_precondition_options(Conversation conversation, FileTransfer file_transfer) { return new Xep.Jet.Options(Omemo.NS_URI, AES_128_GCM_URI); } public FileMeta complete_meta(FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta, Xmpp.Xep.JingleFileTransfer.FileTransfer jingle_transfer) { Xep.Jet.SecurityParameters? security = jingle_transfer.security as Xep.Jet.SecurityParameters; if (security != null && security.encoding.get_type_uri() == Omemo.NS_URI) { file_transfer.encryption = Encryption.OMEMO; } return file_meta; } } }dino-0.1.0/plugins/omemo/src/logic/0000755000000000000000000000000013614354364015631 5ustar rootrootdino-0.1.0/plugins/omemo/src/logic/database.vala0000644000000000000000000003072713614354364020253 0ustar rootrootusing Gee; using Qlite; using Dino.Entities; namespace Dino.Plugins.Omemo { public class Database : Qlite.Database { private const int VERSION = 4; public class IdentityMetaTable : Table { //Default to provide backwards compatability public Column identity_id = new Column.Integer("identity_id") { not_null = true, min_version = 2, default = "-1" }; public Column address_name = new Column.Text("address_name") { not_null = true }; public Column device_id = new Column.Integer("device_id") { not_null = true }; public Column identity_key_public_base64 = new Column.Text("identity_key_public_base64"); public Column trusted_identity = new Column.BoolInt("trusted_identity") { default = "0", max_version = 1 }; public Column trust_level = new Column.Integer("trust_level") { default = TrustLevel.UNKNOWN.to_string(), min_version = 2 }; public Column now_active = new Column.BoolInt("now_active") { default = "1" }; public Column last_active = new Column.Long("last_active"); internal IdentityMetaTable(Database db) { base(db, "identity_meta"); init({identity_id, address_name, device_id, identity_key_public_base64, trusted_identity, trust_level, now_active, last_active}); index("identity_meta_idx", {identity_id, address_name, device_id}, true); index("identity_meta_list_idx", {identity_id, address_name}); } public QueryBuilder with_address(int identity_id, string address_name) { return select().with(this.identity_id, "=", identity_id).with(this.address_name, "=", address_name); } public QueryBuilder get_with_device_id(int identity_id, int device_id) { return select().with(this.identity_id, "=", identity_id).with(this.device_id, "=", device_id); } public void insert_device_list(int32 identity_id, string address_name, ArrayList devices) { update().with(this.identity_id, "=", identity_id).with(this.address_name, "=", address_name).set(now_active, false).perform(); foreach (int32 device_id in devices) { upsert() .value(this.identity_id, identity_id, true) .value(this.address_name, address_name, true) .value(this.device_id, device_id, true) .value(this.now_active, true) .value(this.last_active, (long) new DateTime.now_utc().to_unix()) .perform(); } } public int64 insert_device_bundle(int32 identity_id, string address_name, int device_id, Bundle bundle, TrustLevel trust) { if (bundle == null || bundle.identity_key == null) return -1; // Do not replace identity_key if it was known before, it should never change! string identity_key = Base64.encode(bundle.identity_key.serialize()); RowOption row = with_address(identity_id, address_name).with(this.device_id, "=", device_id).single().row(); if (row.is_present() && row[identity_key_public_base64] != null && row[identity_key_public_base64] != identity_key) { critical("Tried to change the identity key for a known device id. Likely an attack."); return -1; } return upsert() .value(this.identity_id, identity_id, true) .value(this.address_name, address_name, true) .value(this.device_id, device_id, true) .value(this.identity_key_public_base64, identity_key) .value(this.trust_level, trust).perform(); } public int64 insert_device_session(int32 identity_id, string address_name, int device_id, string identity_key, TrustLevel trust) { RowOption row = with_address(identity_id, address_name).with(this.device_id, "=", device_id).single().row(); if (row.is_present() && row[identity_key_public_base64] != null && row[identity_key_public_base64] != identity_key) { critical("Tried to change the identity key for a known device id. Likely an attack."); return -1; } return upsert() .value(this.identity_id, identity_id, true) .value(this.address_name, address_name, true) .value(this.device_id, device_id, true) .value(this.identity_key_public_base64, identity_key) .value(this.trust_level, trust).perform(); } public QueryBuilder get_trusted_devices(int identity_id, string address_name) { return this.with_address(identity_id, address_name) .with(this.trust_level, "!=", TrustLevel.UNTRUSTED) .with(this.now_active, "=", true); } public QueryBuilder get_known_devices(int identity_id, string address_name) { return this.with_address(identity_id, address_name) .with(this.trust_level, "!=", TrustLevel.UNKNOWN) .without_null(this.identity_key_public_base64); } public QueryBuilder get_unknown_devices(int identity_id, string address_name) { return this.with_address(identity_id, address_name) .with_null(this.identity_key_public_base64); } public QueryBuilder get_new_devices(int identity_id, string address_name) { return this.with_address(identity_id, address_name) .with(this.trust_level, "=", TrustLevel.UNKNOWN) .without_null(this.identity_key_public_base64); } public Row? get_device(int identity_id, string address_name, int device_id) { return this.with_address(identity_id, address_name) .with(this.device_id, "=", device_id).single().row().inner; } } public class TrustTable : Table { public Column identity_id = new Column.Integer("identity_id") { not_null = true }; public Column address_name = new Column.Text("address_name"); public Column blind_trust = new Column.BoolInt("blind_trust") { default = "1" } ; internal TrustTable(Database db) { base(db, "trust"); init({identity_id, address_name, blind_trust}); index("trust_idx", {identity_id, address_name}, true); } public bool get_blind_trust(int32 identity_id, string address_name, bool def = false) { RowOption row = this.select().with(this.identity_id, "=", identity_id) .with(this.address_name, "=", address_name).single().row(); if (row.is_present()) return row[blind_trust]; return def; } } public class IdentityTable : Table { public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; public Column account_id = new Column.Integer("account_id") { unique = true, not_null = true }; public Column device_id = new Column.Integer("device_id") { not_null = true }; public Column identity_key_private_base64 = new Column.NonNullText("identity_key_private_base64"); public Column identity_key_public_base64 = new Column.NonNullText("identity_key_public_base64"); internal IdentityTable(Database db) { base(db, "identity"); init({id, account_id, device_id, identity_key_private_base64, identity_key_public_base64}); } public int get_id(int account_id) { int id = -1; Row? row = this.row_with(this.account_id, account_id).inner; if (row != null) id = ((!)row)[this.id]; return id; } } public class SignedPreKeyTable : Table { public Column identity_id = new Column.Integer("identity_id") { not_null = true }; public Column signed_pre_key_id = new Column.Integer("signed_pre_key_id") { not_null = true }; public Column record_base64 = new Column.NonNullText("record_base64"); internal SignedPreKeyTable(Database db) { base(db, "signed_pre_key"); init({identity_id, signed_pre_key_id, record_base64}); unique({identity_id, signed_pre_key_id}); index("signed_pre_key_idx", {identity_id, signed_pre_key_id}, true); } } public class PreKeyTable : Table { public Column identity_id = new Column.Integer("identity_id") { not_null = true }; public Column pre_key_id = new Column.Integer("pre_key_id") { not_null = true }; public Column record_base64 = new Column.NonNullText("record_base64"); internal PreKeyTable(Database db) { base(db, "pre_key"); init({identity_id, pre_key_id, record_base64}); unique({identity_id, pre_key_id}); index("pre_key_idx", {identity_id, pre_key_id}, true); } } public class SessionTable : Table { public Column identity_id = new Column.Integer("identity_id") { not_null = true }; public Column address_name = new Column.NonNullText("name"); public Column device_id = new Column.Integer("device_id") { not_null = true }; public Column record_base64 = new Column.NonNullText("record_base64"); internal SessionTable(Database db) { base(db, "session"); init({identity_id, address_name, device_id, record_base64}); unique({identity_id, address_name, device_id}); index("session_idx", {identity_id, address_name, device_id}, true); } } public class ContentItemMetaTable : Table { public Column content_item_id = new Column.Integer("message_id") { primary_key = true }; public Column identity_id = new Column.Integer("identity_id") { not_null = true }; public Column address_name = new Column.Text("address_name") { not_null = true }; public Column device_id = new Column.Integer("device_id") { not_null = true }; public Column trusted_when_received = new Column.BoolInt("trusted_when_received") { not_null = true, default = "1" }; internal ContentItemMetaTable(Database db) { base(db, "content_item_meta"); init({content_item_id, identity_id, address_name, device_id, trusted_when_received}); index("content_item_meta_device_idx", {identity_id, device_id, address_name}); } public RowOption with_content_item(ContentItem item) { return row_with(content_item_id, item.id); } public QueryBuilder with_device(int identity_id, string address_name, int device_id) { return select() .with(this.identity_id, "=", identity_id) .with(this.address_name, "=", address_name) .with(this.device_id, "=", device_id); } } public IdentityMetaTable identity_meta { get; private set; } public TrustTable trust { get; private set; } public IdentityTable identity { get; private set; } public SignedPreKeyTable signed_pre_key { get; private set; } public PreKeyTable pre_key { get; private set; } public SessionTable session { get; private set; } public ContentItemMetaTable content_item_meta { get; private set; } public Database(string fileName) { base(fileName, VERSION); identity_meta = new IdentityMetaTable(this); trust = new TrustTable(this); identity = new IdentityTable(this); signed_pre_key = new SignedPreKeyTable(this); pre_key = new PreKeyTable(this); session = new SessionTable(this); content_item_meta = new ContentItemMetaTable(this); init({identity_meta, trust, identity, signed_pre_key, pre_key, session, content_item_meta}); try { exec("PRAGMA synchronous=0"); } catch (Error e) { } } public override void migrate(long oldVersion) { if(oldVersion == 1) { try { exec("DROP INDEX identity_meta_idx"); exec("DROP INDEX identity_meta_list_idx"); exec("CREATE UNIQUE INDEX identity_meta_idx ON identity_meta (identity_id, address_name, device_id)"); exec("CREATE INDEX identity_meta_list_idx ON identity_meta (identity_id, address_name)"); } catch (Error e) { stderr.printf("Failed to migrate OMEMO database\n"); Process.exit(-1); } } } } } dino-0.1.0/plugins/omemo/src/logic/encrypt_state.vala0000644000000000000000000000211613614354364021362 0ustar rootrootnamespace Dino.Plugins.Omemo { public class EncryptState { public bool encrypted { get; internal set; } public int other_devices { get; internal set; } public int other_success { get; internal set; } public int other_lost { get; internal set; } public int other_unknown { get; internal set; } public int other_failure { get; internal set; } public int other_waiting_lists { get; internal set; } public int own_devices { get; internal set; } public int own_success { get; internal set; } public int own_lost { get; internal set; } public int own_unknown { get; internal set; } public int own_failure { get; internal set; } public bool own_list { get; internal set; } public string to_string() { return @"EncryptState (encrypted=$encrypted, other=(devices=$other_devices, success=$other_success, lost=$other_lost, unknown=$other_unknown, failure=$other_failure, waiting_lists=$other_waiting_lists, own=(devices=$own_devices, success=$own_success, lost=$own_lost, unknown=$own_unknown, failure=$own_failure, list=$own_list))"; } } } dino-0.1.0/plugins/omemo/src/logic/manager.vala0000644000000000000000000004717313614354364020124 0ustar rootrootusing Dino.Entities; using Signal; using Qlite; using Xmpp; using Gee; namespace Dino.Plugins.Omemo { public class Manager : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("omemo_manager"); public string id { get { return IDENTITY.id; } } private StreamInteractor stream_interactor; private Database db; private TrustManager trust_manager; private Map message_states = new HashMap(Entities.Message.hash_func, Entities.Message.equals_func); private class MessageState { public Entities.Message msg { get; private set; } public EncryptState last_try { get; private set; } public int waiting_other_sessions { get; set; } public int waiting_own_sessions { get; set; } public bool waiting_own_devicelist { get; set; } public int waiting_other_devicelists { get; set; } public bool force_next_attempt { get; set; } public bool will_send_now { get; private set; } public bool active_send_attempt { get; set; } public MessageState(Entities.Message msg, EncryptState last_try) { update_from_encrypt_status(msg, last_try); } public void update_from_encrypt_status(Entities.Message msg, EncryptState new_try) { this.msg = msg; this.last_try = new_try; this.waiting_other_sessions = new_try.other_unknown; this.waiting_own_sessions = new_try.own_unknown; this.waiting_own_devicelist = !new_try.own_list; this.waiting_other_devicelists = new_try.other_waiting_lists; this.active_send_attempt = false; will_send_now = false; if (new_try.other_failure > 0 || (new_try.other_lost == new_try.other_devices && new_try.other_devices > 0)) { msg.marked = Entities.Message.Marked.WONTSEND; } else if (new_try.other_unknown > 0 || new_try.own_unknown > 0 || new_try.other_waiting_lists > 0 || !new_try.own_list || new_try.own_devices == 0) { msg.marked = Entities.Message.Marked.UNSENT; } else if (!new_try.encrypted) { msg.marked = Entities.Message.Marked.WONTSEND; } else { will_send_now = true; } } public bool should_retry_now() { return !waiting_own_devicelist && waiting_other_devicelists <= 0 && waiting_other_sessions <= 0 && waiting_own_sessions <= 0 && !active_send_attempt; } public string to_string() { return @"MessageState (msg=$(msg.stanza_id), send=$will_send_now, $last_try)"; } } private Manager(StreamInteractor stream_interactor, Database db, TrustManager trust_manager) { this.stream_interactor = stream_interactor; this.db = db; this.trust_manager = trust_manager; stream_interactor.stream_negotiated.connect(on_stream_negotiated); stream_interactor.get_module(MessageProcessor.IDENTITY).pre_message_send.connect(on_pre_message_send); stream_interactor.get_module(RosterManager.IDENTITY).mutual_subscription.connect(on_mutual_subscription); } public void clear_device_list(Account account) { XmppStream? stream = stream_interactor.get_stream(account); if (stream == null) return; stream.get_module(StreamModule.IDENTITY).clear_device_list(stream); } private Gee.List get_occupants(Jid jid, Account account){ Gee.List occupants = new ArrayList(Jid.equals_bare_func); if(!stream_interactor.get_module(MucManager.IDENTITY).is_groupchat(jid, account)){ occupants.add(jid); } Gee.List? occupant_jids = stream_interactor.get_module(MucManager.IDENTITY).get_offline_members(jid, account); if(occupant_jids == null) { return occupants; } foreach (Jid occupant in occupant_jids) { if(!occupant.equals(account.bare_jid)){ occupants.add(occupant.bare_jid); } } return occupants; } private void on_pre_message_send(Entities.Message message, Xmpp.MessageStanza message_stanza, Conversation conversation) { if (message.encryption == Encryption.OMEMO) { XmppStream? stream = stream_interactor.get_stream(conversation.account); if (stream == null) { message.marked = Entities.Message.Marked.UNSENT; return; } StreamModule? module_ = ((!)stream).get_module(StreamModule.IDENTITY); if (module_ == null) { message.marked = Entities.Message.Marked.UNSENT; return; } StreamModule module = (!)module_; //Get a list of everyone for whom the message should be encrypted Gee.List recipients; if (message_stanza.type_ == MessageStanza.TYPE_GROUPCHAT) { recipients = get_occupants((!)message.to.bare_jid, conversation.account); if (recipients.size == 0) { message.marked = Entities.Message.Marked.WONTSEND; return; } } else { recipients = new ArrayList(Jid.equals_bare_func); recipients.add(message_stanza.to); } //Attempt to encrypt the message EncryptState enc_state = trust_manager.encrypt(message_stanza, conversation.account.bare_jid, recipients, stream, conversation.account); MessageState state; lock (message_states) { if (message_states.has_key(message)) { state = message_states.get(message); state.update_from_encrypt_status(message, enc_state); if (state.will_send_now) { debug("sending message delayed: %s", state.to_string()); } } else { state = new MessageState(message, enc_state); message_states[message] = state; } if (state.will_send_now) { message_states.unset(message); } } //Encryption failed - need to fetch more information if (!state.will_send_now) { if (message.marked == Entities.Message.Marked.WONTSEND) { debug("retracting message %s", state.to_string()); message_states.unset(message); } else { debug("delaying message %s", state.to_string()); if (state.waiting_own_sessions > 0) { module.fetch_bundles((!)stream, conversation.account.bare_jid, trust_manager.get_trusted_devices(conversation.account, conversation.account.bare_jid)); } if (state.waiting_other_sessions > 0 && message.counterpart != null) { foreach(Jid jid in get_occupants(((!)message.counterpart).bare_jid, conversation.account)) { module.fetch_bundles((!)stream, jid, trust_manager.get_trusted_devices(conversation.account, jid)); } } if (state.waiting_other_devicelists > 0 && message.counterpart != null) { foreach(Jid jid in get_occupants(((!)message.counterpart).bare_jid, conversation.account)) { module.request_user_devicelist.begin((!)stream, jid); } } } } } } private void on_mutual_subscription(Account account, Jid jid) { XmppStream? stream = stream_interactor.get_stream(account); if(stream == null) return; stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).request_user_devicelist.begin((!)stream, jid); } private void on_stream_negotiated(Account account, XmppStream stream) { StreamModule module = stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY); if (module != null) { module.request_user_devicelist.begin(stream, account.bare_jid); module.device_list_loaded.connect((jid, devices) => on_device_list_loaded(account, jid, devices)); module.bundle_fetched.connect((jid, device_id, bundle) => on_bundle_fetched(account, jid, device_id, bundle)); module.bundle_fetch_failed.connect((jid) => continue_message_sending(account, jid)); } initialize_store.begin(account); } private void on_device_list_loaded(Account account, Jid jid, ArrayList device_list) { debug("received device list for %s from %s", account.bare_jid.to_string(), jid.to_string()); XmppStream? stream = stream_interactor.get_stream(account); if (stream == null) { return; } StreamModule? module = ((!)stream).get_module(StreamModule.IDENTITY); if (module == null) { return; } int identity_id = db.identity.get_id(account.id); if (identity_id < 0) return; //Update meta database db.identity_meta.insert_device_list(identity_id, jid.bare_jid.to_string(), device_list); //Fetch the bundle for each new device int inc = 0; foreach (Row row in db.identity_meta.get_unknown_devices(identity_id, jid.bare_jid.to_string())) { try { module.fetch_bundle(stream, new Jid(row[db.identity_meta.address_name]), row[db.identity_meta.device_id], false); inc++; } catch (InvalidJidError e) { warning("Ignoring device with invalid Jid: %s", e.message); } } if (inc > 0) { debug("new bundles %i/%i for %s", inc, device_list.size, jid.to_string()); } //Create an entry for the jid in the account table if one does not exist already if (db.trust.select().with(db.trust.identity_id, "=", identity_id).with(db.trust.address_name, "=", jid.bare_jid.to_string()).count() == 0) { db.trust.insert().value(db.trust.identity_id, identity_id).value(db.trust.address_name, jid.bare_jid.to_string()).value(db.trust.blind_trust, true).perform(); } //Get all messages that needed the devicelist and determine if we can now send them HashSet send_now = new HashSet(); lock (message_states) { foreach (Entities.Message msg in message_states.keys) { if (!msg.account.equals(account)) continue; Gee.List occupants = get_occupants(msg.counterpart.bare_jid, account); MessageState state = message_states[msg]; if (account.bare_jid.equals(jid)) { state.waiting_own_devicelist = false; } else if (msg.counterpart != null && occupants.contains(jid)) { state.waiting_other_devicelists--; } if (state.should_retry_now()) { send_now.add(msg); state.active_send_attempt = true; } } } foreach (Entities.Message msg in send_now) { if (msg.counterpart == null) continue; Entities.Conversation? conv = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(((!)msg.counterpart), account); if (conv == null) continue; stream_interactor.get_module(MessageProcessor.IDENTITY).send_xmpp_message(msg, (!)conv, true); } } private void on_bundle_fetched(Account account, Jid jid, int32 device_id, Bundle bundle) { int identity_id = db.identity.get_id(account.id); if (identity_id < 0) return; bool blind_trust = db.trust.get_blind_trust(identity_id, jid.bare_jid.to_string(), true); //If we don't blindly trust new devices and we haven't seen this key before then don't trust it bool untrust = !(blind_trust || db.identity_meta.with_address(identity_id, jid.bare_jid.to_string()) .with(db.identity_meta.device_id, "=", device_id) .with(db.identity_meta.identity_key_public_base64, "=", Base64.encode(bundle.identity_key.serialize())) .single().row().is_present()); //Get trust information from the database if the device id is known Row device = db.identity_meta.get_device(identity_id, jid.bare_jid.to_string(), device_id); TrustLevel trusted = TrustLevel.UNKNOWN; if (device != null) { trusted = (TrustLevel) device[db.identity_meta.trust_level]; } if(untrust) { trusted = TrustLevel.UNKNOWN; } else if (blind_trust && trusted == TrustLevel.UNKNOWN) { trusted = TrustLevel.TRUSTED; } //Update the database with the appropriate trust information db.identity_meta.insert_device_bundle(identity_id, jid.bare_jid.to_string(), device_id, bundle, trusted); if (should_start_session(account, jid)) { XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) { StreamModule? module = ((!)stream).get_module(StreamModule.IDENTITY); if (module != null) { module.start_session(stream, jid, device_id, bundle); } } } continue_message_sending(account, jid); } private bool should_start_session(Account account, Jid jid) { lock (message_states) { foreach (Entities.Message msg in message_states.keys) { if (!msg.account.equals(account)) continue; Gee.List occupants = get_occupants(msg.counterpart.bare_jid, account); if (account.bare_jid.equals(jid) || (msg.counterpart != null && (msg.counterpart.equals_bare(jid) || occupants.contains(jid)))) { return true; } } } return false; } private void continue_message_sending(Account account, Jid jid) { //Get all messages waiting and determine if they can now be sent HashSet send_now = new HashSet(); lock (message_states) { foreach (Entities.Message msg in message_states.keys) { if (!msg.account.equals(account)) continue; Gee.List occupants = get_occupants(msg.counterpart.bare_jid, account); MessageState state = message_states[msg]; if (account.bare_jid.equals(jid)) { state.waiting_own_sessions--; } else if (msg.counterpart != null && (msg.counterpart.equals_bare(jid) || occupants.contains(jid))) { state.waiting_other_sessions--; } if (state.should_retry_now()){ send_now.add(msg); state.active_send_attempt = true; } } } foreach (Entities.Message msg in send_now) { if (msg.counterpart == null) continue; Entities.Conversation? conv = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation((!)msg.counterpart, account); if (conv == null) continue; stream_interactor.get_module(MessageProcessor.IDENTITY).send_xmpp_message(msg, (!)conv, true); } } private async void initialize_store(Account account) { // If the account is not yet persisted, wait for that and then continue - without identity.account_id the entry isn't worth much. if (account.id == -1) { account.notify["id"].connect(() => initialize_store.callback()); yield; } StreamModule? module = stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY); if (module == null) return; Store store = module.store; Qlite.Row? row = db.identity.row_with(db.identity.account_id, account.id).inner; int identity_id = -1; bool publish_identity = false; if (row == null) { // OMEMO not yet initialized, starting with empty base publish_identity = true; try { store.identity_key_store.local_registration_id = Random.int_range(1, int32.MAX); Signal.ECKeyPair key_pair = Plugin.get_context().generate_key_pair(); store.identity_key_store.identity_key_private = new Bytes(key_pair.private.serialize()); store.identity_key_store.identity_key_public = new Bytes(key_pair.public.serialize()); identity_id = (int) db.identity.insert().or("REPLACE") .value(db.identity.account_id, account.id) .value(db.identity.device_id, (int) store.local_registration_id) .value(db.identity.identity_key_private_base64, Base64.encode(store.identity_key_store.identity_key_private.get_data())) .value(db.identity.identity_key_public_base64, Base64.encode(store.identity_key_store.identity_key_public.get_data())) .perform(); } catch (Error e) { // Ignore error } } else { store.identity_key_store.local_registration_id = ((!)row)[db.identity.device_id]; store.identity_key_store.identity_key_private = new Bytes(Base64.decode(((!)row)[db.identity.identity_key_private_base64])); store.identity_key_store.identity_key_public = new Bytes(Base64.decode(((!)row)[db.identity.identity_key_public_base64])); identity_id = ((!)row)[db.identity.id]; } if (identity_id >= 0) { store.signed_pre_key_store = new BackedSignedPreKeyStore(db, identity_id); store.pre_key_store = new BackedPreKeyStore(db, identity_id); store.session_store = new BackedSessionStore(db, identity_id); } else { warning("store for %s is not persisted!", account.bare_jid.to_string()); } // Generated new device ID, ensure this gets added to the devicelist XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) { module.request_user_devicelist.begin((!)stream, account.bare_jid); } } public async bool ensure_get_keys_for_conversation(Conversation conversation) { if (stream_interactor.get_module(MucManager.IDENTITY).is_private_room(conversation.account, conversation.counterpart)) { foreach (Jid offline_member in stream_interactor.get_module(MucManager.IDENTITY).get_offline_members(conversation.counterpart, conversation.account)) { bool ok = yield ensure_get_keys_for_jid(conversation.account, offline_member); if (!ok) { return false; } } return true; } return yield ensure_get_keys_for_jid(conversation.account, conversation.counterpart.bare_jid); } public async bool ensure_get_keys_for_jid(Account account, Jid jid) { if (trust_manager.is_known_address(account, jid)) return true; XmppStream? stream = stream_interactor.get_stream(account); if (stream != null) { var device_list = yield stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).request_user_devicelist(stream, jid); return device_list.size > 0; } return true; // TODO wait for stream? } public static void start(StreamInteractor stream_interactor, Database db, TrustManager trust_manager) { Manager m = new Manager(stream_interactor, db, trust_manager); stream_interactor.add_module(m); } } } dino-0.1.0/plugins/omemo/src/logic/pre_key_store.vala0000644000000000000000000000256313614354364021356 0ustar rootrootusing Signal; using Qlite; namespace Dino.Plugins.Omemo { private class BackedPreKeyStore : SimplePreKeyStore { private Database db; private int identity_id; public BackedPreKeyStore(Database db, int identity_id) { this.db = db; this.identity_id = identity_id; init(); } private void init() { try { foreach (Row row in db.pre_key.select().with(db.pre_key.identity_id, "=", identity_id)) { store_pre_key(row[db.pre_key.pre_key_id], Base64.decode(row[db.pre_key.record_base64])); } } catch (Error e) { warning("Error while initializing pre key store: %s", e.message); } pre_key_stored.connect(on_pre_key_stored); pre_key_deleted.connect(on_pre_key_deleted); } public void on_pre_key_stored(PreKeyStore.Key key) { db.pre_key.insert().or("REPLACE") .value(db.pre_key.identity_id, identity_id) .value(db.pre_key.pre_key_id, (int) key.key_id) .value(db.pre_key.record_base64, Base64.encode(key.record)) .perform(); } public void on_pre_key_deleted(PreKeyStore.Key key) { db.pre_key.delete() .with(db.pre_key.identity_id, "=", identity_id) .with(db.pre_key.pre_key_id, "=", (int) key.key_id) .perform(); } } } dino-0.1.0/plugins/omemo/src/logic/session_store.vala0000644000000000000000000000317513614354364021403 0ustar rootrootusing Signal; using Qlite; namespace Dino.Plugins.Omemo { private class BackedSessionStore : SimpleSessionStore { private Database db; private int identity_id; public BackedSessionStore(Database db, int identity_id) { this.db = db; this.identity_id = identity_id; init(); } private void init() { try { foreach (Row row in db.session.select().with(db.session.identity_id, "=", identity_id)) { Address addr = new Address(row[db.session.address_name], row[db.session.device_id]); store_session(addr, Base64.decode(row[db.session.record_base64])); addr.device_id = 0; } } catch (Error e) { print("Error while initializing session store: %s", e.message); } session_stored.connect(on_session_stored); session_removed.connect(on_session_deleted); } public void on_session_stored(SessionStore.Session session) { db.session.insert().or("REPLACE") .value(db.session.identity_id, identity_id) .value(db.session.address_name, session.name) .value(db.session.device_id, session.device_id) .value(db.session.record_base64, Base64.encode(session.record)) .perform(); } public void on_session_deleted(SessionStore.Session session) { db.session.delete() .with(db.session.identity_id, "=", identity_id) .with(db.session.address_name, "=", session.name) .with(db.session.device_id, "=", session.device_id) .perform(); } } } dino-0.1.0/plugins/omemo/src/logic/signed_pre_key_store.vala0000644000000000000000000000305113614354364022700 0ustar rootrootusing Qlite; using Signal; namespace Dino.Plugins.Omemo { private class BackedSignedPreKeyStore : SimpleSignedPreKeyStore { private Database db; private int identity_id; public BackedSignedPreKeyStore(Database db, int identity_id) { this.db = db; this.identity_id = identity_id; init(); } private void init() { try { foreach (Row row in db.signed_pre_key.select().with(db.signed_pre_key.identity_id, "=", identity_id)) { store_signed_pre_key(row[db.signed_pre_key.signed_pre_key_id], Base64.decode(row[db.signed_pre_key.record_base64])); } } catch (Error e) { print("Error while initializing signed pre key store: %s", e.message); } signed_pre_key_stored.connect(on_signed_pre_key_stored); signed_pre_key_deleted.connect(on_signed_pre_key_deleted); } public void on_signed_pre_key_stored(SignedPreKeyStore.Key key) { db.signed_pre_key.insert().or("REPLACE") .value(db.signed_pre_key.identity_id, identity_id) .value(db.signed_pre_key.signed_pre_key_id, (int) key.key_id) .value(db.signed_pre_key.record_base64, Base64.encode(key.record)) .perform(); } public void on_signed_pre_key_deleted(SignedPreKeyStore.Key key) { db.signed_pre_key.delete() .with(db.signed_pre_key.identity_id, "=", identity_id) .with(db.signed_pre_key.signed_pre_key_id, "=", (int) key.key_id) .perform(); } } } dino-0.1.0/plugins/omemo/src/logic/trust_manager.vala0000644000000000000000000005323713614354364021363 0ustar rootrootusing Dino.Entities; using Gee; using Xmpp; using Signal; using Qlite; namespace Dino.Plugins.Omemo { public class TrustManager { private StreamInteractor stream_interactor; private Database db; private DecryptMessageListener decrypt_message_listener; private TagMessageListener tag_message_listener; private HashMap message_device_id_map = new HashMap(Message.hash_func, Message.equals_func); public TrustManager(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; this.db = db; decrypt_message_listener = new DecryptMessageListener(stream_interactor, db, message_device_id_map); tag_message_listener = new TagMessageListener(stream_interactor, db, message_device_id_map); stream_interactor.get_module(MessageProcessor.IDENTITY).received_pipeline.connect(decrypt_message_listener); stream_interactor.get_module(MessageProcessor.IDENTITY).received_pipeline.connect(tag_message_listener); } public void set_blind_trust(Account account, Jid jid, bool blind_trust) { int identity_id = db.identity.get_id(account.id); if (identity_id < 0) return; db.trust.update() .with(db.trust.identity_id, "=", identity_id) .with(db.trust.address_name, "=", jid.bare_jid.to_string()) .set(db.trust.blind_trust, blind_trust).perform(); } public void set_device_trust(Account account, Jid jid, int device_id, TrustLevel trust_level) { int identity_id = db.identity.get_id(account.id); db.identity_meta.update() .with(db.identity_meta.identity_id, "=", identity_id) .with(db.identity_meta.address_name, "=", jid.bare_jid.to_string()) .with(db.identity_meta.device_id, "=", device_id) .set(db.identity_meta.trust_level, trust_level).perform(); // Hide messages from untrusted or unknown devices string selection = null; string[] selection_args = {}; var app_db = Application.get_default().db; foreach (Row row in db.content_item_meta.with_device(identity_id, jid.bare_jid.to_string(), device_id).with(db.content_item_meta.trusted_when_received, "=", false)) { if (selection == null) { selection = @"$(app_db.content_item.id) = ?"; } else { selection += @" OR $(app_db.content_item.id) = ?"; } selection_args += row[db.content_item_meta.content_item_id].to_string(); } if (selection != null) { app_db.content_item.update() .set(app_db.content_item.hide, trust_level == TrustLevel.UNTRUSTED || trust_level == TrustLevel.UNKNOWN) .where(selection, selection_args) .perform(); } } private StanzaNode create_encrypted_key_node(uint8[] key, Address address, Store store) throws GLib.Error { SessionCipher cipher = store.create_session_cipher(address); CiphertextMessage device_key = cipher.encrypt(key); debug("Created encrypted key for %s/%d", address.name, address.device_id); StanzaNode key_node = new StanzaNode.build("key", NS_URI) .put_attribute("rid", address.device_id.to_string()) .put_node(new StanzaNode.text(Base64.encode(device_key.serialized))); if (device_key.type == CiphertextType.PREKEY) key_node.put_attribute("prekey", "true"); return key_node; } internal EncryptState encrypt_key(StanzaNode header_node, uint8[] keytag, Jid self_jid, Gee.List recipients, XmppStream stream, Account account) throws Error { EncryptState status = new EncryptState(); StreamModule module = stream.get_module(StreamModule.IDENTITY); //Check we have the bundles and device lists needed to send the message if (!is_known_address(account, self_jid)) return status; status.own_list = true; status.own_devices = get_trusted_devices(account, self_jid).size; status.other_waiting_lists = 0; status.other_devices = 0; foreach (Jid recipient in recipients) { if (!is_known_address(account, recipient)) { status.other_waiting_lists++; } if (status.other_waiting_lists > 0) return status; status.other_devices += get_trusted_devices(account, recipient).size; } if (status.own_devices == 0 || status.other_devices == 0) return status; //Encrypt the key for each recipient's device individually Address address = new Address("", 0); foreach (Jid recipient in recipients) { foreach(int32 device_id in get_trusted_devices(account, recipient)) { if (module.is_ignored_device(recipient, device_id)) { status.other_lost++; continue; } try { address.name = recipient.bare_jid.to_string(); address.device_id = (int) device_id; StanzaNode key_node = create_encrypted_key_node(keytag, address, module.store); header_node.put_node(key_node); status.other_success++; } catch (Error e) { if (e.code == ErrorCode.UNKNOWN) status.other_unknown++; else status.other_failure++; } } } // Encrypt the key for each own device address.name = self_jid.bare_jid.to_string(); foreach(int32 device_id in get_trusted_devices(account, self_jid)) { if (module.is_ignored_device(self_jid, device_id)) { status.own_lost++; continue; } if (device_id != module.store.local_registration_id) { address.device_id = (int) device_id; try { StanzaNode key_node = create_encrypted_key_node(keytag, address, module.store); header_node.put_node(key_node); status.own_success++; } catch (Error e) { if (e.code == ErrorCode.UNKNOWN) status.own_unknown++; else status.own_failure++; } } } return status; } public EncryptState encrypt(MessageStanza message, Jid self_jid, Gee.List recipients, XmppStream stream, Account account) { EncryptState status = new EncryptState(); if (!Plugin.ensure_context()) return status; if (message.to == null) return status; StreamModule module = stream.get_module(StreamModule.IDENTITY); try { //Create a key and use it to encrypt the message uint8[] key = new uint8[16]; Plugin.get_context().randomize(key); uint8[] iv = new uint8[16]; Plugin.get_context().randomize(iv); uint8[] aes_encrypt_result = aes_encrypt(Cipher.AES_GCM_NOPADDING, key, iv, message.body.data); uint8[] ciphertext = aes_encrypt_result[0:aes_encrypt_result.length-16]; uint8[] tag = aes_encrypt_result[aes_encrypt_result.length-16:aes_encrypt_result.length]; uint8[] keytag = new uint8[key.length + tag.length]; Memory.copy(keytag, key, key.length); Memory.copy((uint8*)keytag + key.length, tag, tag.length); StanzaNode header_node; StanzaNode encrypted_node = new StanzaNode.build("encrypted", NS_URI).add_self_xmlns() .put_node(header_node = new StanzaNode.build("header", NS_URI) .put_attribute("sid", module.store.local_registration_id.to_string()) .put_node(new StanzaNode.build("iv", NS_URI) .put_node(new StanzaNode.text(Base64.encode(iv))))) .put_node(new StanzaNode.build("payload", NS_URI) .put_node(new StanzaNode.text(Base64.encode(ciphertext)))); status = encrypt_key(header_node, keytag, self_jid, recipients, stream, account); message.stanza.put_node(encrypted_node); Xep.ExplicitEncryption.add_encryption_tag_to_message(message, NS_URI, "OMEMO"); message.body = "[This message is OMEMO encrypted]"; status.encrypted = true; } catch (Error e) { warning(@"Signal error while encrypting message: $(e.message)\n"); message.body = "[OMEMO encryption failed]"; status.encrypted = false; } return status; } public bool is_known_address(Account account, Jid jid) { int identity_id = db.identity.get_id(account.id); if (identity_id < 0) return false; return db.identity_meta.with_address(identity_id, jid.to_string()).with(db.identity_meta.last_active, ">", 0).count() > 0; } public Gee.List get_trusted_devices(Account account, Jid jid) { Gee.List devices = new ArrayList(); int identity_id = db.identity.get_id(account.id); if (identity_id < 0) return devices; foreach (Row device in db.identity_meta.get_trusted_devices(identity_id, jid.bare_jid.to_string())) { if(device[db.identity_meta.trust_level] != TrustLevel.UNKNOWN || device[db.identity_meta.identity_key_public_base64] == null) devices.add(device[db.identity_meta.device_id]); } return devices; } private class TagMessageListener : MessageListener { public string[] after_actions_const = new string[]{ "STORE" }; public override string action_group { get { return "DECRYPT_TAG"; } } public override string[] after_actions { get { return after_actions_const; } } private StreamInteractor stream_interactor; private Database db; private HashMap message_device_id_map; public TagMessageListener(StreamInteractor stream_interactor, Database db, HashMap message_device_id_map) { this.stream_interactor = stream_interactor; this.db = db; this.message_device_id_map = message_device_id_map; } public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) { int device_id = 0; if (message_device_id_map.has_key(message)) { device_id = message_device_id_map[message]; message_device_id_map.unset(message); } // TODO: Handling of files ContentItem? content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_item(conversation, 1, message.id); if (content_item != null && device_id != 0) { Jid jid = content_item.jid; if (conversation.type_ == Conversation.Type.GROUPCHAT) { jid = message.real_jid; } int identity_id = db.identity.get_id(conversation.account.id); TrustLevel trust_level = (TrustLevel) db.identity_meta.get_device(identity_id, jid.bare_jid.to_string(), device_id)[db.identity_meta.trust_level]; if (trust_level == TrustLevel.UNTRUSTED || trust_level == TrustLevel.UNKNOWN) { stream_interactor.get_module(ContentItemStore.IDENTITY).set_item_hide(content_item, true); } db.content_item_meta.insert() .value(db.content_item_meta.content_item_id, content_item.id) .value(db.content_item_meta.identity_id, identity_id) .value(db.content_item_meta.address_name, jid.bare_jid.to_string()) .value(db.content_item_meta.device_id, device_id) .value(db.content_item_meta.trusted_when_received, trust_level != TrustLevel.UNTRUSTED) .perform(); } return false; } } private class DecryptMessageListener : MessageListener { public string[] after_actions_const = new string[]{ }; public override string action_group { get { return "DECRYPT"; } } public override string[] after_actions { get { return after_actions_const; } } private StreamInteractor stream_interactor; private Database db; private HashMap message_device_id_map; public DecryptMessageListener(StreamInteractor stream_interactor, Database db, HashMap message_device_id_map) { this.stream_interactor = stream_interactor; this.db = db; this.message_device_id_map = message_device_id_map; } public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) { StreamModule module = stream_interactor.module_manager.get_module(conversation.account, StreamModule.IDENTITY); Store store = module.store; StanzaNode? _encrypted = stanza.stanza.get_subnode("encrypted", NS_URI); if (_encrypted == null || MessageFlag.get_flag(stanza) != null || stanza.from == null) return false; StanzaNode encrypted = (!)_encrypted; if (message.body == null && Xep.ExplicitEncryption.get_encryption_tag(stanza) == NS_URI) { message.body = "[This message is OMEMO encrypted]"; // TODO temporary }; if (!Plugin.ensure_context()) return false; int identity_id = db.identity.get_id(conversation.account.id); MessageFlag flag = new MessageFlag(); stanza.add_flag(flag); StanzaNode? _header = encrypted.get_subnode("header"); if (_header == null) return false; StanzaNode header = (!)_header; int sid = header.get_attribute_int("sid"); if (sid <= 0) return false; foreach (StanzaNode key_node in header.get_subnodes("key")) { debug("Is ours? %d =? %u", key_node.get_attribute_int("rid"), store.local_registration_id); if (key_node.get_attribute_int("rid") == store.local_registration_id) { string? payload = encrypted.get_deep_string_content("payload"); string? iv_node = header.get_deep_string_content("iv"); string? key_node_content = key_node.get_string_content(); if (payload == null || iv_node == null || key_node_content == null) continue; uint8[] key; uint8[] ciphertext = Base64.decode((!)payload); uint8[] iv = Base64.decode((!)iv_node); Gee.List possible_jids = new ArrayList(); if (conversation.type_ == Conversation.Type.CHAT) { possible_jids.add(stanza.from.bare_jid); } else { Jid? real_jid = message.real_jid; if (real_jid != null) { possible_jids.add(real_jid.bare_jid); } else if (key_node.get_attribute_bool("prekey")) { // pre key messages do store the identity key, so we can use that to find the real jid PreKeySignalMessage msg = Plugin.get_context().deserialize_pre_key_signal_message(Base64.decode((!)key_node_content)); string identity_key = Base64.encode(msg.identity_key.serialize()); foreach (Row row in db.identity_meta.get_with_device_id(identity_id, sid).with(db.identity_meta.identity_key_public_base64, "=", identity_key)) { try { possible_jids.add(new Jid(row[db.identity_meta.address_name])); } catch (InvalidJidError e) { warning("Ignoring invalid jid from database: %s", e.message); } } if (possible_jids.size != 1) { continue; } } else { // If we don't know the device name (MUC history w/o MAM), test decryption with all keys with fitting device id foreach (Row row in db.identity_meta.get_with_device_id(identity_id, sid)) { try { possible_jids.add(new Jid(row[db.identity_meta.address_name])); } catch (InvalidJidError e) { warning("Ignoring invalid jid from database: %s", e.message); } } } } if (possible_jids.size == 0) { debug("Received message from unknown entity with device id %d", sid); } foreach (Jid possible_jid in possible_jids) { try { Address address = new Address(possible_jid.to_string(), sid); if (key_node.get_attribute_bool("prekey")) { Row? device = db.identity_meta.get_device(identity_id, possible_jid.to_string(), sid); PreKeySignalMessage msg = Plugin.get_context().deserialize_pre_key_signal_message(Base64.decode((!)key_node_content)); string identity_key = Base64.encode(msg.identity_key.serialize()); if (device != null && device[db.identity_meta.identity_key_public_base64] != null) { if (device[db.identity_meta.identity_key_public_base64] != identity_key) { critical("Tried to use a different identity key for a known device id."); continue; } } else { debug("Learn new device from incoming message from %s/%d", possible_jid.to_string(), sid); bool blind_trust = db.trust.get_blind_trust(identity_id, possible_jid.to_string(), true); if (db.identity_meta.insert_device_session(identity_id, possible_jid.to_string(), sid, identity_key, blind_trust ? TrustLevel.TRUSTED : TrustLevel.UNKNOWN) < 0) { critical("Failed learning a device."); continue; } XmppStream? stream = stream_interactor.get_stream(conversation.account); if (device == null && stream != null) { module.request_user_devicelist.begin(stream, possible_jid); } } debug("Starting new session for decryption with device from %s/%d", possible_jid.to_string(), sid); SessionCipher cipher = store.create_session_cipher(address); key = cipher.decrypt_pre_key_signal_message(msg); // TODO: Finish session } else { debug("Continuing session for decryption with device from %s/%d", possible_jid.to_string(), sid); SignalMessage msg = Plugin.get_context().deserialize_signal_message(Base64.decode((!)key_node_content)); SessionCipher cipher = store.create_session_cipher(address); key = cipher.decrypt_signal_message(msg); } //address.device_id = 0; // TODO: Hack to have address obj live longer if (key.length >= 32) { int authtaglength = key.length - 16; uint8[] new_ciphertext = new uint8[ciphertext.length + authtaglength]; uint8[] new_key = new uint8[16]; Memory.copy(new_ciphertext, ciphertext, ciphertext.length); Memory.copy((uint8*)new_ciphertext + ciphertext.length, (uint8*)key + 16, authtaglength); Memory.copy(new_key, key, 16); ciphertext = new_ciphertext; key = new_key; } message.body = arr_to_str(aes_decrypt(Cipher.AES_GCM_NOPADDING, key, iv, ciphertext)); message_device_id_map[message] = address.device_id; message.encryption = Encryption.OMEMO; flag.decrypted = true; } catch (Error e) { debug("Decrypting message from %s/%d failed: %s", possible_jid.to_string(), sid, e.message); continue; } // If we figured out which real jid a message comes from due to decryption working, save it if (conversation.type_ == Conversation.Type.GROUPCHAT && message.real_jid == null) { message.real_jid = possible_jid; } return false; } } } debug("Received OMEMO encryped message that could not be decrypted."); return false; } private string arr_to_str(uint8[] arr) { // null-terminate the array uint8[] rarr = new uint8[arr.length+1]; Memory.copy(rarr, arr, arr.length); return (string)rarr; } } } } dino-0.1.0/plugins/omemo/src/plugin.vala0000644000000000000000000000770113614354364016704 0ustar rootrootusing Dino.Entities; extern const string GETTEXT_PACKAGE; extern const string LOCALE_INSTALL_DIR; namespace Dino.Plugins.Omemo { public class Plugin : RootInterface, Object { public const bool DEBUG = false; private static Signal.Context? _context; public static Signal.Context get_context() { assert(_context != null); return (!)_context; } public static bool ensure_context() { lock(_context) { try { if (_context == null) { _context = new Signal.Context(DEBUG); } return true; } catch (Error e) { return false; } } } public Dino.Application app; public Database db; public EncryptionListEntry list_entry; public AccountSettingsEntry settings_entry; public ContactDetailsProvider contact_details_provider; public DeviceNotificationPopulator device_notification_populator; public OwnNotifications own_notifications; public TrustManager trust_manager; public void registered(Dino.Application app) { ensure_context(); this.app = app; this.db = new Database(Path.build_filename(Application.get_storage_dir(), "omemo.db")); this.list_entry = new EncryptionListEntry(this); this.settings_entry = new AccountSettingsEntry(this); this.contact_details_provider = new ContactDetailsProvider(this); this.device_notification_populator = new DeviceNotificationPopulator(this, this.app.stream_interactor); this.trust_manager = new TrustManager(this.app.stream_interactor, this.db); this.app.plugin_registry.register_encryption_list_entry(list_entry); this.app.plugin_registry.register_account_settings_entry(settings_entry); this.app.plugin_registry.register_contact_details_entry(contact_details_provider); this.app.plugin_registry.register_notification_populator(device_notification_populator); this.app.stream_interactor.module_manager.initialize_account_modules.connect((account, list) => { list.add(new StreamModule()); list.add(new JetOmemo.Module(this)); this.own_notifications = new OwnNotifications(this, this.app.stream_interactor, account); }); app.stream_interactor.get_module(FileManager.IDENTITY).add_file_decryptor(new OmemoFileDecryptor()); app.stream_interactor.get_module(FileManager.IDENTITY).add_file_encryptor(new OmemoFileEncryptor()); JingleFileHelperRegistry.instance.add_encryption_helper(Encryption.OMEMO, new JetOmemo.EncryptionHelper(app.stream_interactor)); Manager.start(this.app.stream_interactor, db, trust_manager); SimpleAction own_keys_action = new SimpleAction("own-keys", VariantType.INT32); own_keys_action.activate.connect((variant) => { foreach(Dino.Entities.Account account in this.app.stream_interactor.get_accounts()) { if(account.id == variant.get_int32()) { ContactDetailsDialog dialog = new ContactDetailsDialog(this, account, account.bare_jid); dialog.set_transient_for((this.app as Gtk.Application).get_active_window()); dialog.present(); } } }); this.app.add_action(own_keys_action); string locales_dir; if (app.search_path_generator != null) { locales_dir = ((!)app.search_path_generator).get_locale_path(GETTEXT_PACKAGE, LOCALE_INSTALL_DIR); } else { locales_dir = LOCALE_INSTALL_DIR; } internationalize(GETTEXT_PACKAGE, locales_dir); } public void shutdown() { // Nothing to do } public bool has_new_devices(Account account, Xmpp.Jid jid) { int identity_id = db.identity.get_id(account.id); if (identity_id < 0) return false; return db.identity_meta.get_new_devices(identity_id, jid.bare_jid.to_string()).count() > 0; } } } dino-0.1.0/plugins/omemo/src/protocol/0000755000000000000000000000000013614354364016375 5ustar rootrootdino-0.1.0/plugins/omemo/src/protocol/bundle.vala0000644000000000000000000000515513614354364020521 0ustar rootrootusing Gee; using Signal; using Xmpp; namespace Dino.Plugins.Omemo { public class Bundle { public StanzaNode? node; public Bundle(StanzaNode? node) { this.node = node; assert(Plugin.ensure_context()); } public int32 signed_pre_key_id { owned get { if (node == null) return -1; string? id = ((!)node).get_deep_attribute("signedPreKeyPublic", "signedPreKeyId"); if (id == null) return -1; return int.parse((!)id); }} public ECPublicKey? signed_pre_key { owned get { if (node == null) return null; string? key = ((!)node).get_deep_string_content("signedPreKeyPublic"); if (key == null) return null; try { return Plugin.get_context().decode_public_key(Base64.decode((!)key)); } catch (Error e) { return null; } }} public uint8[]? signed_pre_key_signature { owned get { if (node == null) return null; string? sig = ((!)node).get_deep_string_content("signedPreKeySignature"); if (sig == null) return null; return Base64.decode((!)sig); }} public ECPublicKey? identity_key { owned get { if (node == null) return null; string? key = ((!)node).get_deep_string_content("identityKey"); if (key == null) return null; try { return Plugin.get_context().decode_public_key(Base64.decode((!)key)); } catch (Error e) { return null; } }} public ArrayList pre_keys { owned get { ArrayList list = new ArrayList(); if (node == null || ((!)node).get_subnode("prekeys") == null) return list; ((!)node).get_deep_subnodes("prekeys", "preKeyPublic") .filter((node) => ((!)node).get_attribute("preKeyId") != null) .map(PreKey.create) .foreach((key) => list.add(key)); return list; }} public class PreKey { private StanzaNode node; public static PreKey create(owned StanzaNode node) { return new PreKey(node); } public PreKey(StanzaNode node) { this.node = node; } public int32 key_id { owned get { return int.parse(node.get_attribute("preKeyId") ?? "-1"); }} public ECPublicKey? key { owned get { string? key = node.get_string_content(); if (key == null) return null; try { return Plugin.get_context().decode_public_key(Base64.decode((!)key)); } catch (Error e) { return null; } }} } } } dino-0.1.0/plugins/omemo/src/protocol/message_flag.vala0000644000000000000000000000066013614354364021661 0ustar rootrootusing Xmpp; namespace Dino.Plugins.Omemo { public class MessageFlag : Xmpp.MessageFlag { public const string id = "omemo"; public bool decrypted = false; public static MessageFlag? get_flag(MessageStanza message) { return (MessageFlag) message.get_flag(NS_URI, id); } public override string get_ns() { return NS_URI; } public override string get_id() { return id; } } }dino-0.1.0/plugins/omemo/src/protocol/stream_module.vala0000644000000000000000000003634013614354364022110 0ustar rootrootusing Gee; using Xmpp; using Xmpp.Xep; using Signal; namespace Dino.Plugins.Omemo { private const string NS_URI = "eu.siacs.conversations.axolotl"; private const string NODE_DEVICELIST = NS_URI + ".devicelist"; private const string NODE_BUNDLES = NS_URI + ".bundles"; private const string NODE_VERIFICATION = NS_URI + ".verification"; private const int NUM_KEYS_TO_PUBLISH = 100; public class StreamModule : XmppStreamModule { public static Xmpp.ModuleIdentity IDENTITY = new Xmpp.ModuleIdentity(NS_URI, "omemo_module"); private static TimeSpan IGNORE_TIME = TimeSpan.MINUTE; public Store store { public get; private set; } private ConcurrentSet active_bundle_requests = new ConcurrentSet(); private HashMap>> active_devicelist_requests = new HashMap>>(Jid.hash_func, Jid.equals_func); private Map device_ignore_time = new HashMap(); public signal void device_list_loaded(Jid jid, ArrayList devices); public signal void bundle_fetched(Jid jid, int device_id, Bundle bundle); public signal void bundle_fetch_failed(Jid jid, int device_id); public StreamModule() { if (Plugin.ensure_context()) { this.store = Plugin.get_context().create_store(); } } public override void attach(XmppStream stream) { stream.get_module(Pubsub.Module.IDENTITY).add_filtered_notification(stream, NODE_DEVICELIST, (stream, jid, id, node) => parse_device_list(stream, jid, id, node), null); } public override void detach(XmppStream stream) {} public async ArrayList request_user_devicelist(XmppStream stream, Jid jid) { var future = active_devicelist_requests[jid]; if (future == null) { var promise = new Promise?>(); future = promise.future; active_devicelist_requests[jid] = future; stream.get_module(Pubsub.Module.IDENTITY).request(stream, jid, NODE_DEVICELIST, (stream, jid, id, node) => { ArrayList device_list = parse_device_list(stream, jid, id, node); promise.set_value(device_list); active_devicelist_requests.unset(jid); }); } try { ArrayList device_list = yield future.wait_async(); return device_list; } catch (FutureError error) { warning("Future error when waiting for device list: %s", error.message); return new ArrayList(); } } public ArrayList parse_device_list(XmppStream stream, Jid jid, string? id, StanzaNode? node_) { ArrayList device_list = new ArrayList(); StanzaNode node = node_ ?? new StanzaNode.build("list", NS_URI).add_self_xmlns(); Jid? my_jid = stream.get_flag(Bind.Flag.IDENTITY).my_jid; if (my_jid == null) return device_list; if (jid.equals_bare(my_jid) && store.local_registration_id != 0) { bool am_on_devicelist = false; foreach (StanzaNode device_node in node.get_subnodes("device")) { int device_id = device_node.get_attribute_int("id"); if (store.local_registration_id == device_id) { am_on_devicelist = true; } } if (!am_on_devicelist) { debug("Not on device list, adding id"); node.put_node(new StanzaNode.build("device", NS_URI).put_attribute("id", store.local_registration_id.to_string())); stream.get_module(Pubsub.Module.IDENTITY).publish.begin(stream, jid, NODE_DEVICELIST, id, node); } publish_bundles_if_needed(stream, jid); } foreach (StanzaNode device_node in node.get_subnodes("device")) { device_list.add(device_node.get_attribute_int("id")); } device_list_loaded(jid, device_list); return device_list; } public void fetch_bundles(XmppStream stream, Jid jid, Gee.List devices) { Address address = new Address(jid.bare_jid.to_string(), 0); foreach(int32 device_id in devices) { if (!is_ignored_device(jid, device_id)) { address.device_id = device_id; try { if (!store.contains_session(address)) { fetch_bundle(stream, jid, device_id); } } catch (Error e) { // Ignore } } } address.device_id = 0; // TODO: Hack to have address obj live longer } public void fetch_bundle(XmppStream stream, Jid jid, int device_id, bool ignore_if_non_present = true) { if (active_bundle_requests.add(jid.bare_jid.to_string() + @":$device_id")) { debug("Asking for bundle for %s/%d", jid.bare_jid.to_string(), device_id); stream.get_module(Pubsub.Module.IDENTITY).request(stream, jid.bare_jid, @"$NODE_BUNDLES:$device_id", (stream, jid, id, node) => { on_other_bundle_result(stream, jid, device_id, id, node, ignore_if_non_present); }); } } public void ignore_device(Jid jid, int32 device_id) { if (device_id <= 0) return; lock (device_ignore_time) { device_ignore_time[jid.bare_jid.to_string() + @":$device_id"] = new DateTime.now_utc(); } } public void unignore_device(Jid jid, int32 device_id) { if (device_id <= 0) return; lock (device_ignore_time) { device_ignore_time.unset(jid.bare_jid.to_string() + @":$device_id"); } } public bool is_ignored_device(Jid jid, int32 device_id) { if (device_id <= 0) return true; lock (device_ignore_time) { string id = jid.bare_jid.to_string() + @":$device_id"; if (device_ignore_time.has_key(id)) { return new DateTime.now_utc().difference(device_ignore_time[id]) < IGNORE_TIME; } } return false; } public void clear_device_list(XmppStream stream) { stream.get_module(Pubsub.Module.IDENTITY).delete_node(stream, null, NODE_DEVICELIST); } private void on_other_bundle_result(XmppStream stream, Jid jid, int device_id, string? id, StanzaNode? node, bool ignore_if_non_present) { if (node == null) { // Device not registered, shouldn't exist if (ignore_if_non_present) { debug("Ignoring device %s/%d: No bundle", jid.bare_jid.to_string(), device_id); stream.get_module(IDENTITY).ignore_device(jid, device_id); } bundle_fetch_failed(jid, device_id); } else { Bundle bundle = new Bundle(node); stream.get_module(IDENTITY).unignore_device(jid, device_id); debug("Received bundle for %s/%d: %s", jid.bare_jid.to_string(), device_id, Base64.encode(bundle.identity_key.serialize())); bundle_fetched(jid, device_id, bundle); } stream.get_module(IDENTITY).active_bundle_requests.remove(jid.bare_jid.to_string() + @":$device_id"); } public bool start_session(XmppStream stream, Jid jid, int32 device_id, Bundle bundle) { bool fail = false; int32 signed_pre_key_id = bundle.signed_pre_key_id; ECPublicKey? signed_pre_key = bundle.signed_pre_key; uint8[] signed_pre_key_signature = bundle.signed_pre_key_signature; ECPublicKey? identity_key = bundle.identity_key; ArrayList pre_keys = bundle.pre_keys; if (signed_pre_key_id < 0 || signed_pre_key == null || identity_key == null || pre_keys.size == 0) { fail = true; } else { int pre_key_idx = Random.int_range(0, pre_keys.size); int32 pre_key_id = pre_keys[pre_key_idx].key_id; ECPublicKey? pre_key = pre_keys[pre_key_idx].key; if (pre_key_id < 0 || pre_key == null) { fail = true; } else { Address address = new Address(jid.bare_jid.to_string(), device_id); try { if (store.contains_session(address)) { return false; } debug("Starting new session for encryption with %s/%d", jid.bare_jid.to_string(), device_id); SessionBuilder builder = store.create_session_builder(address); builder.process_pre_key_bundle(create_pre_key_bundle(device_id, device_id, pre_key_id, pre_key, signed_pre_key_id, signed_pre_key, signed_pre_key_signature, identity_key)); } catch (Error e) { debug("Can't create session with %s/%d: %s", jid.bare_jid.to_string(), device_id, e.message); fail = true; } address.device_id = 0; // TODO: Hack to have address obj live longer } } if (fail) { debug("Ignoring device %s/%d: Bad bundle: %s", jid.bare_jid.to_string(), device_id, bundle.node.to_string()); stream.get_module(IDENTITY).ignore_device(jid, device_id); } return true; } public void publish_bundles_if_needed(XmppStream stream, Jid jid) { if (active_bundle_requests.add(jid.bare_jid.to_string() + @":$(store.local_registration_id)")) { stream.get_module(Pubsub.Module.IDENTITY).request(stream, jid, @"$NODE_BUNDLES:$(store.local_registration_id)", on_self_bundle_result); } } private void on_self_bundle_result(XmppStream stream, Jid jid, string? id, StanzaNode? node) { if (!Plugin.ensure_context()) return; Map keys = new HashMap(); ECPublicKey? identity_key = null; int32 signed_pre_key_id = -1; ECPublicKey? signed_pre_key = null; SignedPreKeyRecord? signed_pre_key_record = null; bool changed = false; if (node == null) { identity_key = store.identity_key_pair.public; changed = true; } else { Bundle bundle = new Bundle(node); foreach (Bundle.PreKey prekey in bundle.pre_keys) { ECPublicKey? key = prekey.key; if (key != null) { keys[prekey.key_id] = (!)key; } } identity_key = bundle.identity_key; signed_pre_key_id = bundle.signed_pre_key_id; signed_pre_key = bundle.signed_pre_key; } try { // Validate IdentityKey if (identity_key == null || store.identity_key_pair.public.compare((!)identity_key) != 0) { changed = true; } IdentityKeyPair identity_key_pair = store.identity_key_pair; // Validate signedPreKeyRecord + ID if (signed_pre_key == null || signed_pre_key_id == -1 || !store.contains_signed_pre_key(signed_pre_key_id) || store.load_signed_pre_key(signed_pre_key_id).key_pair.public.compare((!)signed_pre_key) != 0) { signed_pre_key_id = Random.int_range(1, int32.MAX); // TODO: No random, use ordered number signed_pre_key_record = Plugin.get_context().generate_signed_pre_key(identity_key_pair, signed_pre_key_id); store.store_signed_pre_key((!)signed_pre_key_record); changed = true; } else { signed_pre_key_record = store.load_signed_pre_key(signed_pre_key_id); } // Validate PreKeys Set pre_key_records = new HashSet(); foreach (var entry in keys.entries) { if (store.contains_pre_key(entry.key)) { PreKeyRecord record = store.load_pre_key(entry.key); if (record.key_pair.public.compare(entry.value) == 0) { pre_key_records.add(record); } } } int new_keys = NUM_KEYS_TO_PUBLISH - pre_key_records.size; if (new_keys > 0) { int32 next_id = Random.int_range(1, int32.MAX); // TODO: No random, use ordered number Set new_records = Plugin.get_context().generate_pre_keys((uint)next_id, (uint)new_keys); pre_key_records.add_all(new_records); foreach (PreKeyRecord record in new_records) { store.store_pre_key(record); } changed = true; } if (changed) { publish_bundles.begin(stream, (!)signed_pre_key_record, identity_key_pair, pre_key_records, (int32) store.local_registration_id); } } catch (Error e) { warning(@"Unexpected error while publishing bundle: $(e.message)\n"); } stream.get_module(IDENTITY).active_bundle_requests.remove(jid.bare_jid.to_string() + @":$(store.local_registration_id)"); } public async void publish_bundles(XmppStream stream, SignedPreKeyRecord signed_pre_key_record, IdentityKeyPair identity_key_pair, Set pre_key_records, int32 device_id) throws Error { ECKeyPair tmp; StanzaNode bundle = new StanzaNode.build("bundle", NS_URI) .add_self_xmlns() .put_node(new StanzaNode.build("signedPreKeyPublic", NS_URI) .put_attribute("signedPreKeyId", signed_pre_key_record.id.to_string()) .put_node(new StanzaNode.text(Base64.encode((tmp = signed_pre_key_record.key_pair).public.serialize())))) .put_node(new StanzaNode.build("signedPreKeySignature", NS_URI) .put_node(new StanzaNode.text(Base64.encode(signed_pre_key_record.signature)))) .put_node(new StanzaNode.build("identityKey", NS_URI) .put_node(new StanzaNode.text(Base64.encode(identity_key_pair.public.serialize())))); StanzaNode prekeys = new StanzaNode.build("prekeys", NS_URI); foreach (PreKeyRecord pre_key_record in pre_key_records) { prekeys.put_node(new StanzaNode.build("preKeyPublic", NS_URI) .put_attribute("preKeyId", pre_key_record.id.to_string()) .put_node(new StanzaNode.text(Base64.encode(pre_key_record.key_pair.public.serialize())))); } bundle.put_node(prekeys); yield stream.get_module(Pubsub.Module.IDENTITY).publish(stream, null, @"$NODE_BUNDLES:$device_id", "1", bundle); yield try_make_bundle_public(stream, device_id); } private async void try_make_bundle_public(XmppStream stream, int32 device_id) { DataForms.DataForm? data_form = yield stream.get_module(Pubsub.Module.IDENTITY).request_node_config(stream, null, @"$NODE_BUNDLES:$device_id"); if (data_form == null) return; foreach (DataForms.DataForm.Field field in data_form.fields) { if (field.var == "pubsub#access_model" && field.get_value_string() != Pubsub.ACCESS_MODEL_OPEN) { field.set_value_string(Pubsub.ACCESS_MODEL_OPEN); yield stream.get_module(Pubsub.Module.IDENTITY).submit_node_config(stream, data_form, @"$NODE_BUNDLES:$device_id"); break; } } } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } } dino-0.1.0/plugins/omemo/src/register_plugin.vala0000644000000000000000000000013613614354364020603 0ustar rootrootpublic Type register_plugin(Module module) { return typeof (Dino.Plugins.Omemo.Plugin); } dino-0.1.0/plugins/omemo/src/trust_level.vala0000644000000000000000000000032413614354364017750 0ustar rootrootnamespace Dino.Plugins.Omemo { public enum TrustLevel { VERIFIED, TRUSTED, UNTRUSTED, UNKNOWN; public string to_string() { int val = this; return val.to_string(); } } } dino-0.1.0/plugins/omemo/src/ui/0000755000000000000000000000000013614354364015151 5ustar rootrootdino-0.1.0/plugins/omemo/src/ui/account_settings_entry.vala0000644000000000000000000000110513614354364022610 0ustar rootrootnamespace Dino.Plugins.Omemo { public class AccountSettingsEntry : Plugins.AccountSettingsEntry { private Plugin plugin; public AccountSettingsEntry(Plugin plugin) { this.plugin = plugin; } public override string id { get { return "omemo_identity_key"; }} public override string name { get { return "OMEMO"; }} public override Plugins.AccountSettingsWidget? get_widget(WidgetType type) { if (type == WidgetType.GTK) { return new AccountSettingWidget(plugin); } return null; } } }dino-0.1.0/plugins/omemo/src/ui/account_settings_widget.vala0000644000000000000000000000360313614354364022737 0ustar rootrootusing Gtk; using Dino.Entities; namespace Dino.Plugins.Omemo { public class AccountSettingWidget : Plugins.AccountSettingsWidget, Box { private Plugin plugin; private Label fingerprint; private Account account; private Button btn; public AccountSettingWidget(Plugin plugin) { this.plugin = plugin; fingerprint = new Label("..."); fingerprint.xalign = 0; Border border = new Button().get_style_context().get_padding(StateFlags.NORMAL); fingerprint.margin_top = border.top + 1; fingerprint.margin_start = border.left + 1; fingerprint.visible = true; pack_start(fingerprint); btn = new Button(); btn.image = new Image.from_icon_name("view-list-symbolic", IconSize.BUTTON); btn.relief = ReliefStyle.NONE; btn.visible = false; btn.valign = Align.CENTER; btn.clicked.connect(() => { activated(); ContactDetailsDialog dialog = new ContactDetailsDialog(plugin, account, account.bare_jid); dialog.set_transient_for((Window) get_toplevel()); dialog.present(); }); pack_start(btn, false); } public void set_account(Account account) { this.account = account; btn.visible = false; Qlite.Row? row = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id).inner; if (row == null) { fingerprint.set_markup("%s\n%s".printf(_("Own fingerprint"), _("Will be generated on first connect"))); } else { string res = fingerprint_markup(fingerprint_from_base64(((!)row)[plugin.db.identity.identity_key_public_base64])); fingerprint.set_markup("%s\n%s".printf(_("Own fingerprint"), res)); btn.visible = true; } } public void deactivate() { } } } dino-0.1.0/plugins/omemo/src/ui/contact_details_dialog.vala0000644000000000000000000003750313614354364022505 0ustar rootrootusing Gtk; using Xmpp; using Gee; using Qlite; using Dino.Entities; using Qrencode; using Gdk; namespace Dino.Plugins.Omemo { [GtkTemplate (ui = "/im/dino/Dino/omemo/contact_details_dialog.ui")] public class ContactDetailsDialog : Gtk.Dialog { private Plugin plugin; private Account account; private Jid jid; private bool own = false; private int own_id = 0; private int identity_id = 0; private Signal.Store store; private Set displayed_ids = new HashSet(); [GtkChild] private Label automatically_accept_new_label; [GtkChild] private Label automatically_accept_new_descr; [GtkChild] private Label own_key_label; [GtkChild] private Label new_keys_label; [GtkChild] private Label associated_keys_label; [GtkChild] private Label inactive_expander_label; [GtkChild] private Box own_fingerprint_container; [GtkChild] private Label own_fingerprint_label; [GtkChild] private Box new_keys_container; [GtkChild] private ListBox new_keys_listbox; [GtkChild] private Box keys_container; [GtkChild] private ListBox keys_listbox; [GtkChild] private Expander inactive_keys_expander; [GtkChild] private ListBox inactive_keys_listbox; [GtkChild] private Switch auto_accept_switch; [GtkChild] private Button copy_button; [GtkChild] private Button show_qrcode_button; [GtkChild] private Image qrcode_image; [GtkChild] private Popover qrcode_popover; construct { // If we set the strings in the .ui file, they don't get translated title = _("OMEMO Key Management"); automatically_accept_new_label.label = _("Automatically accept new keys"); automatically_accept_new_descr.label = _("When this contact adds new encryption keys to their account, automatically accept them."); own_key_label.label = _("Own key"); new_keys_label.label = _("New keys"); associated_keys_label.label = _("Associated keys"); inactive_expander_label.label = _("Inactive keys"); } public ContactDetailsDialog(Plugin plugin, Account account, Jid jid) { Object(use_header_bar : Environment.get_variable("GTK_CSD") != "0" ? 1 : 0); this.plugin = plugin; this.account = account; this.jid = jid; if (Environment.get_variable("GTK_CSD") != "0") { (get_header_bar() as HeaderBar).set_subtitle(jid.bare_jid.to_string()); } keys_listbox.row_activated.connect(on_key_entry_clicked); inactive_keys_listbox.row_activated.connect(on_key_entry_clicked); auto_accept_switch.state_set.connect(on_auto_accept_toggled); identity_id = plugin.db.identity.get_id(account.id); if (identity_id < 0) return; Dino.Application? app = Application.get_default() as Dino.Application; if (app != null) { store = app.stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).store; } auto_accept_switch.set_active(plugin.db.trust.get_blind_trust(identity_id, jid.bare_jid.to_string(), true)); // Dialog opened from the account settings menu // Show the fingerprint for this device separately with buttons for a qrcode and to copy if(jid.equals(account.bare_jid)) { own = true; own_id = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id)[plugin.db.identity.device_id]; automatically_accept_new_descr.label = _("When you add new encryption keys to your account, automatically accept them."); own_fingerprint_container.visible = true; string own_b64 = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id)[plugin.db.identity.identity_key_public_base64]; string fingerprint = fingerprint_from_base64(own_b64); own_fingerprint_label.set_markup(fingerprint_markup(fingerprint)); copy_button.clicked.connect(() => {Clipboard.get_default(get_display()).set_text(fingerprint, fingerprint.length);}); int sid = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id)[plugin.db.identity.device_id]; Pixbuf qr_pixbuf = new QRcode(@"xmpp:$(account.bare_jid)?omemo-sid-$(sid)=$(fingerprint)", 2).to_pixbuf(); qr_pixbuf = qr_pixbuf.scale_simple(150, 150, InterpType.NEAREST); Pixbuf pixbuf = new Pixbuf( qr_pixbuf.colorspace, qr_pixbuf.has_alpha, qr_pixbuf.bits_per_sample, 170, 170 ); pixbuf.fill(uint32.MAX); qr_pixbuf.copy_area(0, 0, 150, 150, pixbuf, 10, 10); qrcode_image.set_from_pixbuf(pixbuf); show_qrcode_button.clicked.connect(qrcode_popover.popup); } new_keys_listbox.set_header_func(header_function); keys_listbox.set_header_func(header_function); //Show any new devices for which the user must decide whether to accept or reject foreach (Row device in plugin.db.identity_meta.get_new_devices(identity_id, jid.to_string())) { add_new_fingerprint(device); } //Show the normal devicelist foreach (Row device in plugin.db.identity_meta.get_known_devices(identity_id, jid.to_string())) { if(own && device[plugin.db.identity_meta.device_id] == own_id) { continue; } add_fingerprint(device, (TrustLevel) device[plugin.db.identity_meta.trust_level]); } // Check for unknown devices fetch_unknown_bundles(); } private void fetch_unknown_bundles() { Dino.Application app = Application.get_default() as Dino.Application; XmppStream? stream = app.stream_interactor.get_stream(account); if (stream == null) return; StreamModule? module = stream.get_module(StreamModule.IDENTITY); if (module == null) return; module.bundle_fetched.connect_after((bundle_jid, device_id, bundle) => { if (bundle_jid.equals(jid) && !displayed_ids.contains(device_id)) { Row? device = plugin.db.identity_meta.get_device(identity_id, jid.to_string(), device_id); if (device == null) return; if (auto_accept_switch.active) { add_fingerprint(device, (TrustLevel) device[plugin.db.identity_meta.trust_level]); } else { add_new_fingerprint(device); } } }); foreach (Row device in plugin.db.identity_meta.get_unknown_devices(identity_id, jid.to_string())) { try { module.fetch_bundle(stream, new Jid(device[plugin.db.identity_meta.address_name]), device[plugin.db.identity_meta.device_id], false); } catch (InvalidJidError e) { warning("Ignoring device with invalid Jid: %s", e.message); } } } private void header_function(ListBoxRow row, ListBoxRow? before) { if (row.get_header() == null && before != null) { row.set_header(new Separator(Orientation.HORIZONTAL)); } } private void add_fingerprint(Row device, TrustLevel trust) { string key_base64 = device[plugin.db.identity_meta.identity_key_public_base64]; bool key_active = device[plugin.db.identity_meta.now_active]; if (store != null) { try { Signal.Address address = new Signal.Address(jid.to_string(), device[plugin.db.identity_meta.device_id]); Signal.SessionRecord? session = null; if (store.contains_session(address)) { session = store.load_session(address); string session_key_base64 = Base64.encode(session.state.remote_identity_key.serialize()); if (key_base64 != session_key_base64) { critical("Session and database identity key mismatch!"); key_base64 = session_key_base64; } } } catch (Error e) { print("Error while reading session store: %s", e.message); } } FingerprintRow fingerprint_row = new FingerprintRow(device, key_base64, trust, key_active) { visible = true, activatable = true, hexpand = true }; if (device[plugin.db.identity_meta.now_active]) { keys_container.visible = true; keys_listbox.add(fingerprint_row); } else { inactive_keys_expander.visible=true; inactive_keys_listbox.add(fingerprint_row); } displayed_ids.add(device[plugin.db.identity_meta.device_id]); } private void on_key_entry_clicked(ListBoxRow widget) { FingerprintRow? fingerprint_row = widget as FingerprintRow; if (fingerprint_row == null) return; Row updated_device = plugin.db.identity_meta.get_device(fingerprint_row.row[plugin.db.identity_meta.identity_id], fingerprint_row.row[plugin.db.identity_meta.address_name], fingerprint_row.row[plugin.db.identity_meta.device_id]); ManageKeyDialog manage_dialog = new ManageKeyDialog(updated_device, plugin.db); manage_dialog.set_transient_for((Gtk.Window) get_toplevel()); manage_dialog.present(); manage_dialog.response.connect((response) => { fingerprint_row.update_trust_state(response, fingerprint_row.row[plugin.db.identity_meta.now_active]); update_stored_trust(response, fingerprint_row.row); }); } private bool on_auto_accept_toggled(bool active) { plugin.trust_manager.set_blind_trust(account, jid, active); if (active) { int identity_id = plugin.db.identity.get_id(account.id); if (identity_id < 0) return false; new_keys_container.visible = false; foreach (Row device in plugin.db.identity_meta.get_new_devices(identity_id, jid.to_string())) { plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.TRUSTED); add_fingerprint(device, TrustLevel.TRUSTED); } } return false; } private void update_stored_trust(int response, Row device) { switch (response) { case TrustLevel.TRUSTED: plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.TRUSTED); break; case TrustLevel.UNTRUSTED: plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.UNTRUSTED); break; case TrustLevel.VERIFIED: plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.VERIFIED); plugin.trust_manager.set_blind_trust(account, jid, false); auto_accept_switch.set_active(false); break; } } private void add_new_fingerprint(Row device) { new_keys_container.visible = true; ListBoxRow lbr = new ListBoxRow() { visible = true, activatable = false, hexpand = true }; Box box = new Box(Gtk.Orientation.HORIZONTAL, 40) { visible = true, margin_start = 20, margin_end = 20, margin_top = 14, margin_bottom = 14, hexpand = true }; Button accept_button = new Button() { visible = true, valign = Align.CENTER, hexpand = true }; accept_button.add(new Image.from_icon_name("emblem-ok-symbolic", IconSize.BUTTON) { visible=true }); // using .image = sets .image-button. Together with .suggested/destructive action that breaks the button Adwaita accept_button.get_style_context().add_class("suggested-action"); accept_button.tooltip_text = _("Accept key"); Button reject_button = new Button() { visible = true, valign = Align.CENTER, hexpand = true }; reject_button.add(new Image.from_icon_name("action-unavailable-symbolic", IconSize.BUTTON) { visible=true }); reject_button.get_style_context().add_class("destructive-action"); reject_button.tooltip_text = _("Reject key"); accept_button.clicked.connect(() => { plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.TRUSTED); add_fingerprint(device, TrustLevel.TRUSTED); new_keys_listbox.remove(lbr); if (new_keys_listbox.get_children().length() < 1) new_keys_container.visible = false; }); reject_button.clicked.connect(() => { plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.UNTRUSTED); add_fingerprint(device, TrustLevel.UNTRUSTED); new_keys_listbox.remove(lbr); if (new_keys_listbox.get_children().length() < 1) new_keys_container.visible = false; }); string res = fingerprint_markup(fingerprint_from_base64(device[plugin.db.identity_meta.identity_key_public_base64])); Label fingerprint_label = new Label(res) { use_markup=true, justify=Justification.RIGHT, visible=true, halign = Align.START, valign = Align.CENTER, hexpand = false }; box.add(fingerprint_label); Box control_box = new Box(Gtk.Orientation.HORIZONTAL, 0) { visible = true, hexpand = true }; control_box.add(accept_button); control_box.add(reject_button); control_box.get_style_context().add_class("linked"); // .linked: Visually link the accept / reject buttons box.add(control_box); lbr.add(box); new_keys_listbox.add(lbr); displayed_ids.add(device[plugin.db.identity_meta.device_id]); } } public class FingerprintRow : ListBoxRow { private Image trust_image = new Image() { visible = true, halign = Align.END, icon_size = IconSize.BUTTON }; private Label fingerprint_label = new Label("") { use_markup=true, justify=Justification.RIGHT, visible=true, halign = Align.START, valign = Align.CENTER, hexpand = false }; private Label trust_label = new Label(null) { visible = true, hexpand = true, xalign = 0 }; public Row row; construct { Box box = new Box(Gtk.Orientation.HORIZONTAL, 40) { visible = true, margin_start = 20, margin_end = 20, margin_top = 14, margin_bottom = 14, hexpand = true }; Box status_box = new Box(Gtk.Orientation.HORIZONTAL, 5) { visible = true, hexpand = true }; box.add(fingerprint_label); box.add(status_box); status_box.add(trust_label); status_box.add(trust_image); this.add(box); } public FingerprintRow(Row row, string key_base64, int trust, bool now_active) { this.row = row; fingerprint_label.label = fingerprint_markup(fingerprint_from_base64(key_base64)); update_trust_state(trust, now_active); } public void update_trust_state(int trust, bool now_active) { switch(trust) { case TrustLevel.TRUSTED: trust_image.icon_name = "emblem-ok-symbolic"; trust_label.set_markup("%s".printf(_("Accepted"))); fingerprint_label.get_style_context().remove_class("dim-label"); break; case TrustLevel.UNTRUSTED: trust_image.icon_name = "action-unavailable-symbolic"; trust_label.set_markup("%s".printf(_("Rejected"))); fingerprint_label.get_style_context().add_class("dim-label"); break; case TrustLevel.VERIFIED: trust_image.icon_name = "security-high-symbolic"; trust_label.set_markup("%s".printf(_("Verified"))); fingerprint_label.get_style_context().remove_class("dim-label"); break; } if (!now_active) { trust_image.icon_name = "appointment-missed-symbolic"; trust_label.set_markup("%s".printf(_("Unused"))); } } } } dino-0.1.0/plugins/omemo/src/ui/contact_details_provider.vala0000644000000000000000000000340113614354364023066 0ustar rootrootusing Gtk; using Gee; using Qlite; using Dino.Entities; namespace Dino.Plugins.Omemo { public class ContactDetailsProvider : Plugins.ContactDetailsProvider, Object { public string id { get { return "omemo_info"; } } private Plugin plugin; public ContactDetailsProvider(Plugin plugin) { this.plugin = plugin; } public void populate(Conversation conversation, Plugins.ContactDetails contact_details, WidgetType type) { if (conversation.type_ == Conversation.Type.CHAT && type == WidgetType.GTK) { int identity_id = plugin.db.identity.get_id(conversation.account.id); if (identity_id < 0) return; int i = 0; foreach (Row row in plugin.db.identity_meta.with_address(identity_id, conversation.counterpart.to_string())) { if (row[plugin.db.identity_meta.identity_key_public_base64] != null) { i++; } } if (i > 0) { Button btn = new Button.from_icon_name("view-list-symbolic") { visible = true, valign = Align.CENTER, relief = ReliefStyle.NONE }; btn.clicked.connect(() => { btn.activate(); ContactDetailsDialog dialog = new ContactDetailsDialog(plugin, conversation.account, conversation.counterpart); dialog.set_transient_for((Window) btn.get_toplevel()); dialog.response.connect((response_type) => { plugin.device_notification_populator.should_hide(); }); dialog.present(); }); contact_details.add(_("Encryption"), "OMEMO", n("%d OMEMO device", "%d OMEMO devices", i).printf(i), btn); } } } } } dino-0.1.0/plugins/omemo/src/ui/device_notification_populator.vala0000644000000000000000000000663413614354364024141 0ustar rootrootusing Dino.Entities; using Xmpp; using Gtk; namespace Dino.Plugins.Omemo { public class DeviceNotificationPopulator : NotificationPopulator, Object { public string id { get { return "device_notification"; } } private StreamInteractor? stream_interactor; private Plugin plugin; private Conversation? current_conversation; private NotificationCollection? notification_collection; private ConversationNotification notification; public DeviceNotificationPopulator(Plugin plugin, StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; this.plugin = plugin; stream_interactor.account_added.connect(on_account_added); } public void init(Conversation conversation, NotificationCollection notification_collection, Plugins.WidgetType type) { current_conversation = conversation; this.notification_collection = notification_collection; if (plugin.has_new_devices(conversation.account, conversation.counterpart) && conversation.type_ == Conversation.Type.CHAT) { display_notification(); } } public void close(Conversation conversation) { notification = null; } private void display_notification() { if (notification == null) { notification = new ConversationNotification(plugin, current_conversation.account, current_conversation.counterpart); notification.should_hide.connect(should_hide); notification_collection.add_meta_notification(notification); } } public void should_hide() { if (!plugin.has_new_devices(current_conversation.account, current_conversation.counterpart) && notification != null){ notification_collection.remove_meta_notification(notification); notification = null; } } private void on_account_added(Account account) { stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).bundle_fetched.connect_after((jid, device_id, bundle) => { if (current_conversation != null && jid.equals(current_conversation.counterpart) && plugin.has_new_devices(current_conversation.account, current_conversation.counterpart)) { display_notification(); } }); } } private class ConversationNotification : MetaConversationNotification { private Widget widget; private Plugin plugin; private Jid jid; private Account account; public signal void should_hide(); public ConversationNotification(Plugin plugin, Account account, Jid jid) { this.plugin = plugin; this.jid = jid; this.account = account; Box box = new Box(Orientation.HORIZONTAL, 5) { visible=true }; Button manage_button = new Button() { label=_("Manage"), visible=true }; manage_button.clicked.connect(() => { manage_button.activate(); ContactDetailsDialog dialog = new ContactDetailsDialog(plugin, account, jid); dialog.set_transient_for((Window) manage_button.get_toplevel()); dialog.response.connect((response_type) => { should_hide(); }); dialog.present(); }); box.add(new Label(_("This contact has new devices")) { margin_end=10, visible=true }); box.add(manage_button); widget = box; } public override Object? get_widget(WidgetType type) { return widget; } } } dino-0.1.0/plugins/omemo/src/ui/encryption_list_entry.vala0000644000000000000000000000366013614354364022471 0ustar rootrootusing Xmpp; namespace Dino.Plugins.Omemo { public class EncryptionListEntry : Plugins.EncryptionListEntry, Object { private Plugin plugin; public EncryptionListEntry(Plugin plugin) { this.plugin = plugin; } public Entities.Encryption encryption { get { return Entities.Encryption.OMEMO; }} public string name { get { return "OMEMO"; }} public void encryption_activated(Entities.Conversation conversation, Plugins.SetInputFieldStatus input_status_callback) { encryption_activated_async.begin(conversation, input_status_callback); } public async void encryption_activated_async(Entities.Conversation conversation, Plugins.SetInputFieldStatus input_status_callback) { MucManager muc_manager = plugin.app.stream_interactor.get_module(MucManager.IDENTITY); Manager omemo_manager = plugin.app.stream_interactor.get_module(Manager.IDENTITY); if (muc_manager.is_private_room(conversation.account, conversation.counterpart)) { foreach (Jid offline_member in muc_manager.get_offline_members(conversation.counterpart, conversation.account)) { bool ok = yield omemo_manager.ensure_get_keys_for_jid(conversation.account, offline_member); if (!ok) { input_status_callback(new Plugins.InputFieldStatus("A member does not support OMEMO: %s".printf(offline_member.to_string()), Plugins.InputFieldStatus.MessageType.ERROR, Plugins.InputFieldStatus.InputState.NO_SEND)); return; } } return; } if (!(yield omemo_manager.ensure_get_keys_for_jid(conversation.account, conversation.counterpart.bare_jid))) { input_status_callback(new Plugins.InputFieldStatus("This contact does not support %s encryption".printf("OMEMO"), Plugins.InputFieldStatus.MessageType.ERROR, Plugins.InputFieldStatus.InputState.NO_SEND)); } } } } dino-0.1.0/plugins/omemo/src/ui/manage_key_dialog.vala0000644000000000000000000002057213614354364021443 0ustar rootrootusing Gtk; using Qlite; namespace Dino.Plugins.Omemo { [GtkTemplate (ui = "/im/dino/Dino/omemo/manage_key_dialog.ui")] public class ManageKeyDialog : Gtk.Dialog { [GtkChild] private HeaderBar headerbar; [GtkChild] private Stack manage_stack; [GtkChild] private Button cancel_button; [GtkChild] private Button ok_button; [GtkChild] private Label main_desc_label; [GtkChild] private ListBox main_action_list; [GtkChild] private Image confirm_image; [GtkChild] private Label confirm_title_label; [GtkChild] private Label confirm_desc_label; [GtkChild] private Label verify_label; [GtkChild] private Label compare_fingerprint_label; [GtkChild] private Button verify_yes_button; [GtkChild] private Button verify_no_button; private Row device; private Database db; private bool return_to_main; private int current_response; construct { // If we set the strings in the .ui file, they don't get translated headerbar.title = _("Manage Key"); compare_fingerprint_label.label = _("Compare the fingerprint, character by character, with the one shown on your contacts device."); verify_no_button.label = _("Not matching"); verify_yes_button.label = _("Matching"); cancel_button.label = _("Cancel"); ok_button.label = _("Confirm"); } public ManageKeyDialog(Row device, Database db) { Object(use_header_bar : Environment.get_variable("GTK_CSD") != "0" ? 1 : 0); this.device = device; this.db = db; setup_main_screen(); setup_verify_screen(); cancel_button.clicked.connect(handle_cancel); ok_button.clicked.connect(() => { response(current_response); close(); }); verify_yes_button.clicked.connect(() => { confirm_image.set_from_icon_name("security-high-symbolic", IconSize.DIALOG); confirm_title_label.label = _("Verify key"); confirm_desc_label.set_markup(_("Once confirmed, any future messages sent by %s using this key will be highlighted accordingly in the chat window.").printf(@"$(device[db.identity_meta.address_name])")); manage_stack.set_visible_child_name("confirm"); ok_button.sensitive = true; return_to_main = false; current_response = TrustLevel.VERIFIED; }); verify_no_button.clicked.connect(() => { return_to_main = false; confirm_image.set_from_icon_name("dialog-warning-symbolic", IconSize.DIALOG); confirm_title_label.label = _("Fingerprints do not match"); confirm_desc_label.set_markup(_("Please verify that you are comparing the correct fingerprint. If fingerprints do not match, %s's account may be compromised and you should consider rejecting this key.").printf(@"$(device[db.identity_meta.address_name])")); manage_stack.set_visible_child_name("confirm"); }); } private void handle_cancel() { if (manage_stack.get_visible_child_name() == "main") close(); if (manage_stack.get_visible_child_name() == "verify") { manage_stack.set_visible_child_name("main"); cancel_button.label = _("Cancel"); } if (manage_stack.get_visible_child_name() == "confirm") { if (return_to_main) { manage_stack.set_visible_child_name("main"); cancel_button.label = _("Cancel"); } else { manage_stack.set_visible_child_name("verify"); } } ok_button.sensitive = false; } private Box make_action_box(string title, string desc){ Box box = new Box(Orientation.VERTICAL, 0) { visible = true, margin_start = 20, margin_end = 20, margin_top = 14, margin_bottom = 14 }; Label lbl_title = new Label(title) { visible = true, halign = Align.START }; Label lbl_desc = new Label(desc) { visible = true, xalign = 0, wrap = true, max_width_chars = 40 }; Pango.AttrList title_attrs = new Pango.AttrList(); title_attrs.insert(Pango.attr_scale_new(1.1)); lbl_title.attributes = title_attrs; Pango.AttrList desc_attrs = new Pango.AttrList(); desc_attrs.insert(Pango.attr_scale_new(0.8)); lbl_desc.attributes = desc_attrs; lbl_desc.get_style_context().add_class("dim-label"); box.add(lbl_title); box.add(lbl_desc); return box; } private void setup_main_screen() { main_action_list.set_header_func((row, before_row) => { if (row.get_header() == null && before_row != null) { row.set_header(new Separator(Orientation.HORIZONTAL)); } }); ListBoxRow verify_row = new ListBoxRow() { visible = true }; verify_row.add(make_action_box(_("Verify key fingerprint"), _("Compare this key's fingerprint with the fingerprint displayed on the contact's device."))); ListBoxRow reject_row = new ListBoxRow() { visible = true }; reject_row.add(make_action_box(_("Reject key"), _("Stop accepting this key during communication with its associated contact."))); ListBoxRow accept_row = new ListBoxRow() {visible = true }; accept_row.add(make_action_box(_("Accept key"), _("Start accepting this key during communication with its associated contact"))); switch((TrustLevel) device[db.identity_meta.trust_level]) { case TrustLevel.TRUSTED: main_desc_label.set_markup(_("This key is currently %s.").printf(""+_("accepted")+"")+" "+_("This means it can be used by %s to receive and send messages.").printf(@"$(device[db.identity_meta.address_name])")); main_action_list.add(verify_row); main_action_list.add(reject_row); break; case TrustLevel.VERIFIED: main_desc_label.set_markup(_("This key is currently %s.").printf(""+_("verified")+"")+" "+_("This means it can be used by %s to receive and send messages.").printf(@"$(device[db.identity_meta.address_name])") + " " + _("Additionally it has been verified to match the key on the contact's device.")); main_action_list.add(reject_row); break; case TrustLevel.UNTRUSTED: main_desc_label.set_markup(_("This key is currently %s.").printf(""+_("rejected")+"")+" "+_("This means it cannot be used by %s to receive messages, and any messages sent by it will be ignored.").printf(@"$(device[db.identity_meta.address_name])")); main_action_list.add(accept_row); break; } //Row clicked - go to appropriate screen main_action_list.row_activated.connect((row) => { if(row == verify_row) { manage_stack.set_visible_child_name("verify"); } else if (row == reject_row) { confirm_image.set_from_icon_name("action-unavailable-symbolic", IconSize.DIALOG); confirm_title_label.label = _("Reject key"); confirm_desc_label.set_markup(_("Once confirmed, any future messages sent by %s using this key will be ignored and none of your messages will be readable using this key.").printf(@"$(device[db.identity_meta.address_name])")); manage_stack.set_visible_child_name("confirm"); ok_button.sensitive = true; return_to_main = true; current_response = TrustLevel.UNTRUSTED; } else if (row == accept_row) { confirm_image.set_from_icon_name("emblem-ok-symbolic", IconSize.DIALOG); confirm_title_label.label = _("Accept key"); confirm_desc_label.set_markup(_("Once confirmed this key will be usable by %s to receive and send messages.").printf(@"$(device[db.identity_meta.address_name])")); manage_stack.set_visible_child_name("confirm"); ok_button.sensitive = true; return_to_main = true; current_response = TrustLevel.TRUSTED; } cancel_button.label = _("Back"); }); manage_stack.set_visible_child_name("main"); } private void setup_verify_screen() { verify_label.set_markup(fingerprint_markup(fingerprint_from_base64(device[db.identity_meta.identity_key_public_base64]))); } } } dino-0.1.0/plugins/omemo/src/ui/own_notifications.vala0000644000000000000000000000244613614354364021560 0ustar rootrootusing Dino.Entities; using Xmpp; using Gtk; namespace Dino.Plugins.Omemo { public class OwnNotifications { private StreamInteractor stream_interactor; private Plugin plugin; private Account account; public OwnNotifications (Plugin plugin, StreamInteractor stream_interactor, Account account) { this.stream_interactor = (!)stream_interactor; this.plugin = plugin; this.account = account; stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).bundle_fetched.connect_after((jid, device_id, bundle) => { if (jid.equals(account.bare_jid) && plugin.has_new_devices(account, account.bare_jid)) { display_notification(); } }); if (plugin.has_new_devices(account, account.bare_jid)) { display_notification(); } } private void display_notification() { Notification notification = new Notification(_("OMEMO trust decision required")); notification.set_default_action_and_target_value("app.own-keys", new Variant.int32(account.id)); notification.set_body(_("Did you add a new device for account %s?").printf(@"$(account.bare_jid.to_string())")); plugin.app.send_notification(account.id.to_string()+"-new-device", notification); } } } dino-0.1.0/plugins/omemo/src/ui/util.vala0000644000000000000000000000346213614354364017000 0ustar rootrootusing Xmpp.Util; namespace Dino.Plugins.Omemo { public static string fingerprint_from_base64(string b64) { uint8[] arr = Base64.decode(b64); arr = arr[1:arr.length]; string s = ""; foreach (uint8 i in arr) { string tmp = i.to_string("%x"); if (tmp.length == 1) tmp = "0" + tmp; s = s + tmp; } return s; } public static string fingerprint_markup(string s) { string markup = ""; for (int i = 0; i < s.length; i += 4) { string four_chars = s.substring(i, 4).down(); int raw = (int) from_hex(four_chars); uint8[] bytes = {(uint8) ((raw >> 8) & 0xff - 128), (uint8) (raw & 0xff - 128)}; Checksum checksum = new Checksum(ChecksumType.SHA1); checksum.update(bytes, bytes.length); uint8[] digest = new uint8[20]; size_t len = 20; checksum.get_digest(digest, ref len); uint8 r = digest[0]; uint8 g = digest[1]; uint8 b = digest[2]; if (r == 0 && g == 0 && b == 0) r = g = b = 1; double brightness = 0.2126 * r + 0.7152 * g + 0.0722 * b; if (brightness < 80) { double factor = 80.0 / brightness; r = uint8.min(255, (uint8) (r * factor)); g = uint8.min(255, (uint8) (g * factor)); b = uint8.min(255, (uint8) (b * factor)); } else if (brightness > 180) { double factor = 180.0 / brightness; r = (uint8) (r * factor); g = (uint8) (g * factor); b = (uint8) (b * factor); } if (i % 32 == 0 && i != 0) markup += "\n"; markup += @"$four_chars"; if (i % 8 == 4 && i % 32 != 28) markup += " "; } return "" + markup + ""; } } dino-0.1.0/plugins/omemo/vapi/0000755000000000000000000000000013614354364014704 5ustar rootrootdino-0.1.0/plugins/omemo/vapi/libqrencode.vapi0000644000000000000000000000254313614354364020060 0ustar rootrootusing Gdk; [CCode (cheader_filename = "qrencode.h")] namespace Qrencode { [CCode (cname = "QRecLevel", cprefix = "QR_ECLEVEL_")] public enum ECLevel { L, M, Q, H } [CCode (cname = "QRencodeMode", cprefix = "QR_MODE_")] public enum EncodeMode { NUL, NUM, AN, [CCode (cname = "QR_MODE_8")] EIGHT_BIT, KANJI, STRUCTURE, ECI, FNC1FIRST, FNC1SECOND } [CCode (cname = "QRcode", free_function = "QRcode_free", has_type_id = false)] [Compact] public class QRcode { private int version; private int width; [CCode (array_length = false)] private uint8[] data; [CCode (cname = "QRcode_encodeString")] public QRcode (string str, int version = 0, ECLevel level = ECLevel.L, EncodeMode hint = EncodeMode.EIGHT_BIT, bool casesensitive = true); public Pixbuf to_pixbuf() { uint8[] bitmap = new uint8[3*width*width]; for (int i = 0; i < width*width; i++) { uint8 color = (data[i] & 1) == 1 ? 0 : 255; bitmap[i*3] = color; bitmap[i*3+1] = color; bitmap[i*3+2] = color; } return new Pixbuf.from_data(bitmap, Colorspace.RGB, false, 8, width, width, width*3); } } } dino-0.1.0/plugins/openpgp/0000755000000000000000000000000013614354364014301 5ustar rootrootdino-0.1.0/plugins/openpgp/CMakeLists.txt0000644000000000000000000000351313614354364017043 0ustar rootrootset(GETTEXT_PACKAGE "dino-openpgp") find_package(Gettext) include(${GETTEXT_USE_FILE}) gettext_compile(${GETTEXT_PACKAGE} SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/po TARGET_NAME ${GETTEXT_PACKAGE}-translations) find_packages(OPENPGP_PACKAGES REQUIRED Gee GLib>=2.38 GModule GObject GTK3 ) set(RESOURCE_LIST account_settings_item.ui ) compile_gresources( OPENPGP_GRESOURCES_TARGET OPENPGP_GRESOURCES_XML TARGET ${CMAKE_CURRENT_BINARY_DIR}/resources/resources.c TYPE EMBED_C RESOURCES ${RESOURCE_LIST} PREFIX /im/dino/Dino/openpgp SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/data ) vala_precompile(OPENPGP_VALA_C SOURCES src/file_transfer/file_decryptor.vala src/file_transfer/file_encryptor.vala src/account_settings_entry.vala src/account_settings_widget.vala src/contact_details_provider.vala src/database.vala src/encryption_list_entry.vala src/manager.vala src/plugin.vala src/register_plugin.vala src/stream_flag.vala src/stream_module.vala src/util.vala CUSTOM_VAPIS ${CMAKE_BINARY_DIR}/exports/gpgme.vapi ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi ${CMAKE_BINARY_DIR}/exports/qlite.vapi ${CMAKE_BINARY_DIR}/exports/dino.vapi PACKAGES ${OPENPGP_PACKAGES} GRESOURCES ${OPENPGP_GRESOURCES_XML} ) add_definitions(${VALA_CFLAGS} -DG_LOG_DOMAIN="OpenPGP" -DGETTEXT_PACKAGE=\"${GETTEXT_PACKAGE}\" -DLOCALE_INSTALL_DIR=\"${LOCALE_INSTALL_DIR}\") add_library(openpgp SHARED ${OPENPGP_VALA_C} ${OPENPGP_GRESOURCES_TARGET}) add_dependencies(openpgp ${GETTEXT_PACKAGE}-translations) target_link_libraries(openpgp libdino gpgme-vala ${OPENPGP_PACKAGES}) set_target_properties(openpgp PROPERTIES PREFIX "") set_target_properties(openpgp PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins/) install(TARGETS openpgp ${PLUGIN_INSTALL}) dino-0.1.0/plugins/openpgp/data/0000755000000000000000000000000013614354364015212 5ustar rootrootdino-0.1.0/plugins/openpgp/data/account_settings_item.ui0000644000000000000000000000220413614354364022141 0ustar rootroot dino-0.1.0/plugins/openpgp/po/0000755000000000000000000000000013614354364014717 5ustar rootrootdino-0.1.0/plugins/openpgp/po/LINGUAS0000644000000000000000000000012113614354364015736 0ustar rootrootar ca de en es eu fi fr gl hu it ja lb nb nl nl_BE pl pt_BR ro ru sv zh_CN zh_TW dino-0.1.0/plugins/openpgp/po/ar.po0000644000000000000000000000343213614354364015663 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2018-10-23 17:22+0000\n" "Language-Team: Arabic \n" "Language: ar\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " "&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n" "X-Generator: Weblate 3.3-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "ู†ุดุฑ ุงู„ู…ูุงุชูŠุญ ู…ุนุทู„" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "ุฎุทุฃ ููŠ GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "ู„ุง ู…ูุชุงุญ ู…ุชูˆูุฑ. ู‚ู… ุจุชูˆู„ูŠุฏ ูˆุงุญุฏ !" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "ุงุฎุชูŠุงุฑ ู…ูุชุงุญ" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loadingโ€ฆ" msgstr "ุฌุงุฑู ุงู„ุชุญู…ูŠู„โ€ฆ" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "ุงุณุชุนู„ุงู… GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "ุงู„ู…ูุชุงุญ ู„ูŠุณ ููŠ ุณู„ุณู„ุฉ ุงู„ู…ูุงุชูŠุญ" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "ุงู„ุชุดููŠุฑ" dino-0.1.0/plugins/openpgp/po/ca.po0000644000000000000000000000301513614354364015641 0ustar rootroot# Catalan translation for dino-openpgp. # This file is distributed under the same license as the dino package. # Jordi Mallach , 2018. # msgid "" msgstr "" "Project-Id-Version: dino-openpgp 20180123\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2018-01-24 11:24+0100\n" "Language-Team: Catalan \n" "Language: ca\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Publicaciรณ de claus inhabilitada" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "S'ha produรฏt un error al GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "No hi ha claus disponibles. Genereu una!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Seleccioneu una clau" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loadingโ€ฆ" msgstr "S'estร  carregantโ€ฆ" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "S'estร  consultant al GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "La clau no estร  al clauer" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Xifratge" dino-0.1.0/plugins/openpgp/po/de.po0000644000000000000000000000277713614354364015664 0ustar rootrootmsgid "" msgstr "" "Project-Id-Version: dino-openpgp-0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2018-01-24 12:15+0000\n" "Language-Team: German \n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 2.19-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Schlรผsselverรถffentlichung deaktiviert" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Fehler in GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Keine Schlรผssel vorhanden. Erzeuge einen!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Wรคhle Schlรผssel" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loadingโ€ฆ" msgstr "Ladeโ€ฆ" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "Frage GnuPG ab" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "Schlรผssel nicht im Schlรผsselbund" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Verschlรผsselung" #~ msgid "OpenPGP" #~ msgstr "OpenPGP" dino-0.1.0/plugins/openpgp/po/dino-openpgp.pot0000644000000000000000000000262213614354364020044 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loadingโ€ฆ" msgstr "" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "" dino-0.1.0/plugins/openpgp/po/en.po0000644000000000000000000000211113614354364015654 0ustar rootrootmsgid "" msgstr "" "Project-Id-Version: dino-openpgp-0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "Language: en\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loadingโ€ฆ" msgstr "" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "" dino-0.1.0/plugins/openpgp/po/es.po0000644000000000000000000000322213614354364015665 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2018-11-19 08:07+0000\n" "Language-Team: Spanish \n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.3-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Publicaciรณn de claves desactivada" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Error en GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "No hay claves disponibles. ยกGenera una!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Seleccionar clave" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loadingโ€ฆ" msgstr "Cargandoโ€ฆ" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "Consultando GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "La clave no estรก en la cadena de claves" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Cifrado" dino-0.1.0/plugins/openpgp/po/eu.po0000644000000000000000000000320013614354364015663 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2018-04-03 15:27+0000\n" "Language-Team: Basque \n" "Language: eu\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 2.20-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Gakoa argitaratzea ezgaituta" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "GnuPG akatsa" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Ez dago gakorik eskuragarri. Sortu ezazu bat!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Gakoa hautatu" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loadingโ€ฆ" msgstr "Kargatzenโ€ฆ" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "GnuPGri galdetzen" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "Gakorik ez giltzatakoan" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Enkriptazioa" dino-0.1.0/plugins/openpgp/po/fi.po0000644000000000000000000000251413614354364015657 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Language-Team: none\n" "Language: fi\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loadingโ€ฆ" msgstr "" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "" dino-0.1.0/plugins/openpgp/po/fr.po0000644000000000000000000000325613614354364015674 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2018-01-24 12:13+0000\n" "Language-Team: French \n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" "X-Generator: Weblate 2.19-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "La publication des clefs est dรฉsactivรฉe" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Erreur dans GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Aucune clef nโ€™est disponible. Gรฉnรฉrez en une !" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Choix dโ€™une clรฉ" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loadingโ€ฆ" msgstr "Chargementโ€ฆ" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "Interrogation de GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "La clef nโ€™est pas dans le trousseau" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Chiffrement" dino-0.1.0/plugins/openpgp/po/gl.po0000644000000000000000000000321213614354364015657 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2018-09-29 03:26+0000\n" "Language-Team: Galician \n" "Language: gl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.2-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Desactivada a publicaciรณn de chaves" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Fallo en GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Sen chaves dispoรฑibles. Crear unha!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Escolla chave" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loadingโ€ฆ" msgstr "Cargandoโ€ฆ" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "Consultando a GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "A chave non estรก no anel de chaves" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Cifrado" dino-0.1.0/plugins/openpgp/po/hu.po0000644000000000000000000000322213614354364015672 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2018-08-31 08:38+0000\n" "Language-Team: Hungarian \n" "Language: hu\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.2-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "A kulcs publikรกlรกsa kikapcsolva" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Hiba a GnuPG-ben" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Nincs elรฉrhetล‘ kulcs. Generรกlj egyet!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Vรกlassz kulcsot" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loadingโ€ฆ" msgstr "Betรถltรฉsโ€ฆ" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "A GnuPG lekรฉrรฉse" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "A kulcs nincs a kulcstartรณban" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Titkosรญtรกs" dino-0.1.0/plugins/openpgp/po/it.po0000644000000000000000000000300713614354364015673 0ustar rootrootmsgid "" msgstr "" "Project-Id-Version: dino-openpgp-0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2018-01-24 12:18+0000\n" "Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 2.19-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Pubblicazione della chiave disabilitata" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Errore in GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Nessuna chiave disponibile. Generane una!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Seleziona una chiave" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loadingโ€ฆ" msgstr "Caricamentoโ€ฆ" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "Interrogando GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "La chiave non รจ nel portachiavi" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Crittografia" #~ msgid "OpenPGP" #~ msgstr "OpenPGP" dino-0.1.0/plugins/openpgp/po/ja.po0000644000000000000000000000317213614354364015654 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2020-01-28 23:24+0000\n" "Language-Team: none\n" "Language: ja\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 3.11-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "ใ‚ญใƒผใฎ็™บ่กŒใฏ็„กๅŠน" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "GnuPG ใงใฎใ‚จใƒฉใƒผ" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "ใ‚ญใƒผใŒใ‚ใ‚Šใพใ›ใ‚“ใ€‚็”Ÿๆˆใ—ใพใ—ใ‚‡ใ†๏ผ" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "ใ‚ญใƒผใ‚’้ธๆŠž" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loadingโ€ฆ" msgstr "่ชญใฟ่พผใ‚“ใงใ„ใพใ™โ€ฆ" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "GnuPG ใซๅ•ใ„ๅˆใ‚ใ›ใฆใ„ใพใ™" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "ใ‚ญใƒผใฏใ‚ญใƒผใƒใ‚งใƒผใƒณใซใ‚ใ‚Šใพใ›ใ‚“" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "ๆš—ๅทๅŒ–" dino-0.1.0/plugins/openpgp/po/lb.po0000644000000000000000000000303013614354364015650 0ustar rootrootmsgid "" msgstr "" "Project-Id-Version: dino-openpgp-0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2018-01-24 12:20+0000\n" "Language-Team: Luxembourgish \n" "Language: lb\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 2.19-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Schlรซssel Verรซffentlechungย ausgeschalt" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Feeler am GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Keng Schlรซsseleย verfรผgbar. Generรฉierย een!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Schlรซssel auswielen" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loadingโ€ฆ" msgstr "Luedenโ€ฆ" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "GnuPG gรซttย ofgefrot" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "Schlรซsselen net am Schlรซsselbond" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Verschlรซsselung" #~ msgid "OpenPGP" #~ msgstr "OpenPGP" dino-0.1.0/plugins/openpgp/po/nb.po0000644000000000000000000000330013614354364015652 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2017-11-18 00:23+0000\n" "Language-Team: Norwegian Bokmรฅl \n" "Language: nb\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 2.18-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Nรธkkelpublisering avskrudd" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Feil i GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Ingen nรธkler tilgjengelige. Generer รฉn." #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Velg nรธkkel" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loadingโ€ฆ" msgstr "Lasterโ€ฆ" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "Sender spรธrring til GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "Nรธkkelen finnes ikke i nรธkkelknippet" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Kryptering" #~ msgid "OpenPGP" #~ msgstr "OpenPGP" dino-0.1.0/plugins/openpgp/po/nl.po0000644000000000000000000000276513614354364015702 0ustar rootrootmsgid "" msgstr "" "Project-Id-Version: dino-openpgp-0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2017-11-17 20:52+0000\n" "Language-Team: Dutch \n" "Language: nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 2.18-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Publiceren van sleutels uitgeschakeld" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Fout in GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Geen sleutels beschikbaar. Genereer er een!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Selecteer een sleutel" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loadingโ€ฆ" msgstr "Ladenโ€ฆ" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "GnuPG inroepen" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "Sleutel niet in sleutelbos" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Versleuteling" #~ msgid "OpenPGP" #~ msgstr "OpenPGP" dino-0.1.0/plugins/openpgp/po/nl_BE.po0000644000000000000000000000322513614354364016240 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2018-12-26 15:08+0000\n" "Language-Team: Flemish \n" "Language: nl_BE\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.4-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Publiceren van sleutels uitgeschakeld" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Fout in GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Geen sleutels beschikbaar. Genereert er enen!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Selecteert ne sleutel" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loadingโ€ฆ" msgstr "Ladenโ€ฆ" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "GnuPG inroepen" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "Sleutel niet in sleutelbos" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Versleuteling" dino-0.1.0/plugins/openpgp/po/pl.po0000644000000000000000000000330113614354364015667 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2020-01-20 21:21+0000\n" "Language-Team: Polish \n" "Language: pl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2;\n" "X-Generator: Weblate 3.11-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Publikowanie kluczy wyล‚ฤ…czone" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Bล‚ฤ…d w GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Brak kluczy. Utwรณrz nowy!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Wybierz klucz" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loadingโ€ฆ" msgstr "Proszฤ™ czekaฤ‡โ€ฆ" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "Odpytujฤ™ GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "Klucz nie znajduje siฤ™ w pฤ™ku kluczy" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Szyfrowanie" dino-0.1.0/plugins/openpgp/po/pt_BR.po0000644000000000000000000000310613614354364016265 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2020-01-16 00:21+0000\n" "Language-Team: none\n" "Language: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" "X-Generator: Weblate 3.11-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Publicaรงรฃo da chave desativada" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Erro no GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Nenhuma chave disponรญvel. Gere uma!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Selecionar chave" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loadingโ€ฆ" msgstr "Carregandoโ€ฆ" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "Consultando GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "A chave nรฃo estรก no chaveiro" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Criptografia" dino-0.1.0/plugins/openpgp/po/ro.po0000644000000000000000000000330413614354364015677 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2018-01-12 23:14+0000\n" "Language-Team: Romanian \n" "Language: ro\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < " "20)) ? 1 : 2;\n" "X-Generator: Weblate 2.19-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Cheie publicฤƒ dezactivatฤƒ" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Eroare รฎn GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Nici o cheie disponibilฤƒ. Generaศ›i una!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Selectaศ›i cheia" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loadingโ€ฆ" msgstr "Se รฎncarcฤƒโ€ฆ" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "Se interogheazฤƒ GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "Cheia nu este รฎn ศ™irul de chei" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Criptare" dino-0.1.0/plugins/openpgp/po/ru.po0000644000000000000000000000346213614354364015712 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2018-03-23 18:26+0000\n" "Language-Team: Russian \n" "Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "X-Generator: Weblate 2.20-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "ะŸัƒะฑะปะธะบะฐั†ะธั ะบะปัŽั‡ะฐ ะพั‚ะบะปัŽั‡ะตะฝะฐ" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "ะžัˆะธะฑะบะฐ ะฒ GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "ะะตั‚ ะดะพัั‚ัƒะฟะฝั‹ั… ะบะปัŽั‡ะตะน. ะกั‚ะพะธั‚ ัะณะตะฝะตั€ะธั€ะพะฒะฐั‚ัŒ!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "ะ’ั‹ะฑั€ะฐั‚ัŒ ะบะปัŽั‡" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loadingโ€ฆ" msgstr "ะ—ะฐะณั€ัƒะทะบะฐโ€ฆ" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "ะ—ะฐะฟั€ะพั GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "ะะตั‚ ะบะปัŽั‡ะฐ ะฒ ัะฒัะทะบะต" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "ะจะธั„ั€ะพะฒะฐะฝะธะต" dino-0.1.0/plugins/openpgp/po/sv.po0000644000000000000000000000307013614354364015707 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2019-08-14 11:23+0000\n" "Language-Team: none\n" "Language: sv\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 3.8-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "Nyckelpublicering inaktiverad" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "Fel i GnuPG" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "Inga nycklar tillgรคngliga. Generera en!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "Vรคlj nyckel" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loadingโ€ฆ" msgstr "Laddarโ€ฆ" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "Frรฅgar GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "Nyckeln finns inte i nyckelknippan" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "Kryptering" dino-0.1.0/plugins/openpgp/po/zh_CN.po0000644000000000000000000000322413614354364016261 0ustar rootrootmsgid "" msgstr "" "Project-Id-Version: dino-omemo-0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2018-05-08 09:42+0000\n" "Language-Team: Chinese (Simplified) \n" "Language: zh_Hans\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 3.0-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "ๅทฒ็ฆ็”จๅฏ†้’ฅๅ‘ๅธƒๅŠŸ่ƒฝ" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "GnuPG ้”™่ฏฏ" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "ๆฒกๆœ‰ๅฏ†้’ฅๅฏ็”จใ€‚็”Ÿๆˆไธ€ไธชๅง๏ผ" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "้€‰ๆ‹ฉๅฏ†้’ฅ" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loadingโ€ฆ" msgstr "่ฝฝๅ…ฅไธญโ€ฆ" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "ๆญฃๅœจๆŸฅ่ฏข GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "ๅฏ†้’ฅๅนถๆฒกๆœ‰ๅœจๅฏ†้’ฅไธฒ้‡Œ" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "ๅŠ ๅฏ†" #~ msgid "Own fingerprint" #~ msgstr "่‡ชๅทฑ็š„ๆŒ‡็บน" #~ msgid "Will be generated on first connect" #~ msgstr "ไผšๅœจ็ฌฌไธ€ๆฌก่ฟžๆŽฅๆ—ถ็”Ÿๆˆ" #~ msgid "Database error" #~ msgstr "ๆ•ฐๆฎๅบ“้”™่ฏฏ" dino-0.1.0/plugins/openpgp/po/zh_TW.po0000644000000000000000000000320213614354364016307 0ustar rootroot# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-29 00:32+0100\n" "PO-Revision-Date: 2019-01-14 14:19+0000\n" "Language-Team: Chinese (Traditional) \n" "Language: zh_Hant\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 3.4-dev\n" #: plugins/openpgp/src/account_settings_widget.vala:60 #: plugins/openpgp/src/account_settings_widget.vala:64 #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Key publishing disabled" msgstr "้‡‘้‘ฐ็™ผไฝˆๅทฒ่ขซ็ฆ็”จ" #: plugins/openpgp/src/account_settings_widget.vala:60 msgid "Error in GnuPG" msgstr "GnuPG ้Œฏ่ชค" #: plugins/openpgp/src/account_settings_widget.vala:64 msgid "No keys available. Generate one!" msgstr "ๆฒ’ๆœ‰ๅฏ็”จ็š„้‡‘้‘ฐใ€‚็”Ÿๆˆไธ€ๅ€‹!" #: plugins/openpgp/src/account_settings_widget.vala:93 msgid "Select key" msgstr "้ธๆ“‡้‡‘้‘ฐ" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Loadingโ€ฆ" msgstr "่ผ‰ๅ…ฅไธญโ€ฆ" #: plugins/openpgp/src/account_settings_widget.vala:106 msgid "Querying GnuPG" msgstr "ๆญฃๅœจๆŸฅ่ฉข GnuPG" #: plugins/openpgp/src/contact_details_provider.vala:28 msgid "Key not in keychain" msgstr "ๅฏ†้‘ฐไธๅœจๅฏ†้‘ฐไธฒไธญ" #: plugins/openpgp/src/contact_details_provider.vala:30 msgid "Encryption" msgstr "ๅŠ ๅฏ†" dino-0.1.0/plugins/openpgp/src/0000755000000000000000000000000013614354364015070 5ustar rootrootdino-0.1.0/plugins/openpgp/src/account_settings_entry.vala0000644000000000000000000000110713614354364022531 0ustar rootrootnamespace Dino.Plugins.OpenPgp { public class AccountSettingsEntry : Plugins.AccountSettingsEntry { private Plugin plugin; public AccountSettingsEntry(Plugin plugin) { this.plugin = plugin; } public override string id { get { return "pgp_key_picker"; }} public override string name { get { return "OpenPGP"; }} public override Plugins.AccountSettingsWidget? get_widget(WidgetType type) { if (type == WidgetType.GTK) { return new AccountSettingsWidget(plugin); } return null; } } }dino-0.1.0/plugins/openpgp/src/account_settings_widget.vala0000644000000000000000000001142313614354364022655 0ustar rootrootusing Gee; using Gtk; using Dino.Entities; namespace Dino.Plugins.OpenPgp { [GtkTemplate (ui = "/im/dino/Dino/openpgp/account_settings_item.ui")] private class AccountSettingsWidget : Stack, Plugins.AccountSettingsWidget { [GtkChild] private Label label; [GtkChild] private Button button; [GtkChild] private ComboBox combobox; private Plugin plugin; private Account current_account; private Gee.List keys = null; private Gtk.ListStore list_store = new Gtk.ListStore(2, typeof(string), typeof(string?)); public AccountSettingsWidget(Plugin plugin) { this.plugin = plugin; CellRendererText renderer = new CellRendererText(); renderer.set_padding(0, 0); combobox.pack_start(renderer, true); combobox.add_attribute(renderer, "markup", 0); combobox.set_model(list_store); button.clicked.connect(on_button_clicked); combobox.changed.connect(key_changed); } public void deactivate() { set_visible_child_name("label"); } public void set_account(Account account) { set_account_.begin(account); } private async void set_account_(Account account) { this.current_account = account; if (keys == null) { yield fetch_keys(); populate_list_store(); } activate_current_account(); } private void on_button_clicked() { activated(); set_visible_child_name("entry"); combobox.grab_focus(); combobox.popup(); } private void activate_current_account() { combobox.changed.disconnect(key_changed); if (keys == null) { label.set_markup(build_markup_string(_("Key publishing disabled"), _("Error in GnuPG"))); return; } if (keys.size == 0) { label.set_markup(build_markup_string(_("Key publishing disabled"), _("No keys available. Generate one!"))); return; } string? account_key = plugin.db.get_account_key(current_account); int activate_index = 0; for (int i = 0; i < keys.size; i++) { GPG.Key key = keys[i]; if (key.fpr == account_key) { activate_index = i + 1; } } combobox.active = activate_index; TreeIter selected; combobox.get_active_iter(out selected); set_label_active(selected); combobox.changed.connect(key_changed); } private void populate_list_store() { if (keys == null || keys.size == 0) { return; } list_store.clear(); TreeIter iter; list_store.append(out iter); list_store.set(iter, 0, build_markup_string(_("Key publishing disabled"), _("Select key") + " \n "), 1, ""); for (int i = 0; i < keys.size; i++) { list_store.append(out iter); list_store.set(iter, 0, @"$(Markup.escape_text(keys[i].uids[0].uid))\n$(markup_colorize_id(keys[i].fpr, true)) "); list_store.set(iter, 1, keys[i].fpr); if (keys[i].fpr == plugin.db.get_account_key(current_account)) { set_label_active(iter, i + 1); } } button.sensitive = true; } private async void fetch_keys() { label.set_markup(build_markup_string(_("Loadingโ€ฆ"), _("Querying GnuPG"))); SourceFunc callback = fetch_keys.callback; new Thread (null, () => { // Querying GnuPG might take some time try { keys = GPGHelper.get_keylist(null, true); } catch (Error e) { } Idle.add((owned)callback); return null; }); yield; } private void set_label_active(TreeIter iter, int i = -1) { Value text; list_store.get_value(iter, 0, out text); label.set_markup((string) text); if (i != -1) combobox.active = i; } private void key_changed() { TreeIter selected; bool iter_valid = combobox.get_active_iter(out selected); if (iter_valid) { Value key_value; list_store.get_value(selected, 1, out key_value); string? key_id = key_value as string; if (key_id != null) { if (plugin.modules.has_key(current_account)) { plugin.modules[current_account].set_private_key_id(key_id); } plugin.db.set_account_key(current_account, key_id); } set_label_active(selected); deactivate(); } } private string build_markup_string(string primary, string secondary) { return @"$(Markup.escape_text(primary))\n$secondary"; } } } dino-0.1.0/plugins/openpgp/src/contact_details_provider.vala0000644000000000000000000000261513614354364023013 0ustar rootrootusing Gtk; using Dino.Entities; namespace Dino.Plugins.OpenPgp { public class ContactDetailsProvider : Plugins.ContactDetailsProvider, Object { public string id { get { return "pgp_info"; } } private StreamInteractor stream_interactor; public ContactDetailsProvider(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public void populate(Conversation conversation, Plugins.ContactDetails contact_details, WidgetType type) { if (conversation.type_ == Conversation.Type.CHAT && type == WidgetType.GTK) { string? key_id = stream_interactor.get_module(Manager.IDENTITY).get_key_id(conversation.account, conversation.counterpart); if (key_id != null) { Label label = new Label("") { use_markup=true, justify=Justification.RIGHT, selectable=true, visible=true }; Gee.List? keys = null; try { keys = GPGHelper.get_keylist(key_id); } catch (Error e) { } if (keys != null && keys.size > 0) { label.label = markup_colorize_id(keys[0].fpr, true); } else { label.label = _("Key not in keychain") + "\n" + markup_colorize_id(key_id, false); } contact_details.add(_("Encryption"), "OpenPGP", "", label); } } } } } dino-0.1.0/plugins/openpgp/src/database.vala0000644000000000000000000000432513614354364017505 0ustar rootrootusing Qlite; using Dino.Entities; using Xmpp; namespace Dino.Plugins.OpenPgp { public class Database : Qlite.Database { private const int VERSION = 0; public class AccountSetting : Table { public Column account_id = new Column.Integer("account_id") { primary_key = true }; public Column key = new Column.Text("key") { not_null = true }; internal AccountSetting(Database db) { base(db, "account_setting"); init({account_id, key}); } } public class ContactKey : Table { public Column jid = new Column.Text("jid") { primary_key = true }; public Column key = new Column.Text("key") { not_null = true }; internal ContactKey(Database db) { base(db, "contact_key"); init({jid, key}); } } public AccountSetting account_setting_table { get; private set; } public ContactKey contact_key_table { get; private set; } public Database(string filename) { base(filename, VERSION); this.account_setting_table = new AccountSetting(this); this.contact_key_table = new ContactKey(this); init({account_setting_table, contact_key_table}); } public void set_contact_key(Jid jid, string key) { contact_key_table.insert().or("REPLACE") .value(contact_key_table.jid, jid.to_string()) .value(contact_key_table.key, key) .perform(); } public string? get_contact_key(Jid jid) { return contact_key_table.select({contact_key_table.key}) .with(contact_key_table.jid, "=", jid.to_string())[contact_key_table.key]; } public void set_account_key(Account account, string key) { account_setting_table.insert().or("REPLACE") .value(account_setting_table.account_id, account.id) .value(account_setting_table.key, key) .perform(); } public string? get_account_key(Account account) { return account_setting_table.select({account_setting_table.key}) .with(account_setting_table.account_id, "=", account.id)[account_setting_table.key]; } public override void migrate(long oldVersion) { } } } dino-0.1.0/plugins/openpgp/src/encryption_list_entry.vala0000644000000000000000000000571113614354364022407 0ustar rootrootusing Gee; using Dino.Entities; using Xmpp; namespace Dino.Plugins.OpenPgp { private class EncryptionListEntry : Plugins.EncryptionListEntry, Object { private StreamInteractor stream_interactor; private Database db; public EncryptionListEntry(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; this.db = db; } public Entities.Encryption encryption { get { return Encryption.PGP; }} public string name { get { return "OpenPGP"; }} public void encryption_activated(Entities.Conversation conversation, Plugins.SetInputFieldStatus input_status_callback) { try { GPGHelper.get_public_key(db.get_account_key(conversation.account) ?? ""); } catch (Error e) { input_status_callback(new Plugins.InputFieldStatus("You didn't configure OpenPGP for this account. You can do that in the Accounts Dialog.", Plugins.InputFieldStatus.MessageType.ERROR, Plugins.InputFieldStatus.InputState.NO_SEND)); return; } if (conversation.type_ == Conversation.Type.CHAT) { string? key_id = stream_interactor.get_module(Manager.IDENTITY).get_key_id(conversation.account, conversation.counterpart); if (key_id == null) { input_status_callback(new Plugins.InputFieldStatus("This contact does not support %s encryption.".printf("OpenPGP"), Plugins.InputFieldStatus.MessageType.ERROR, Plugins.InputFieldStatus.InputState.NO_SEND)); return; } try { GPGHelper.get_keylist(key_id); } catch (Error e) { input_status_callback(new Plugins.InputFieldStatus("This contact's OpenPGP key is not in your keyring.", Plugins.InputFieldStatus.MessageType.ERROR, Plugins.InputFieldStatus.InputState.NO_SEND)); } } else if (conversation.type_ == Conversation.Type.GROUPCHAT) { Gee.List muc_jids = new Gee.ArrayList(); Gee.List? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_occupants(conversation.counterpart, conversation.account); if (occupants != null) muc_jids.add_all(occupants); Gee.List? offline_members = stream_interactor.get_module(MucManager.IDENTITY).get_offline_members(conversation.counterpart, conversation.account); if (offline_members != null) muc_jids.add_all(offline_members); foreach (Jid jid in muc_jids) { string? key_id = stream_interactor.get_module(Manager.IDENTITY).get_key_id(conversation.account, jid); if (key_id == null) { input_status_callback(new Plugins.InputFieldStatus("A member's OpenPGP key is not in your keyring: %s / %s.".printf(jid.to_string(), key_id), Plugins.InputFieldStatus.MessageType.ERROR, Plugins.InputFieldStatus.InputState.NO_SEND)); return; } } } } } } dino-0.1.0/plugins/openpgp/src/file_transfer/0000755000000000000000000000000013614354364017713 5ustar rootrootdino-0.1.0/plugins/openpgp/src/file_transfer/file_decryptor.vala0000644000000000000000000000413513614354364023575 0ustar rootrootusing Dino.Entities; namespace Dino.Plugins.OpenPgp { public class PgpFileDecryptor : FileDecryptor, Object { public FileReceiveData prepare_get_meta_info(Conversation conversation, FileTransfer file_transfer, FileReceiveData receive_data) { return receive_data; } public FileMeta prepare_download_file(Conversation conversation, FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta) { return file_meta; } public bool can_decrypt_file(Conversation conversation, FileTransfer file_transfer, FileReceiveData receive_data) { return file_transfer.file_name.has_suffix("pgp") || file_transfer.mime_type == "application/pgp-encrypted"; } public async InputStream decrypt_file(InputStream encrypted_stream, Conversation conversation, FileTransfer file_transfer, FileReceiveData receive_data) throws FileReceiveError { try { uint8[] buf = new uint8[256]; ByteArray data = new ByteArray(); size_t len = -1; do { len = yield encrypted_stream.read_async(buf); data.append(buf[0:len]); } while(len > 0); GPGHelper.DecryptedData clear_data = GPGHelper.decrypt_data(data.data); file_transfer.encryption = Encryption.PGP; if (clear_data.filename != null && clear_data.filename != "") { debug("Decrypting file %s from %s", clear_data.filename, file_transfer.file_name); file_transfer.file_name = clear_data.filename; } else if (file_transfer.file_name.has_suffix(".pgp")) { debug("Decrypting file %s from %s", file_transfer.file_name.substring(0, file_transfer.file_name.length - 4), file_transfer.file_name); file_transfer.file_name = file_transfer.file_name.substring(0, file_transfer.file_name.length - 4); } return new MemoryInputStream.from_data(clear_data.data, GLib.free); } catch (Error e) { throw new FileReceiveError.DECRYPTION_FAILED("PGP file decryption error: %s".printf(e.message)); } } } } dino-0.1.0/plugins/openpgp/src/file_transfer/file_encryptor.vala0000644000000000000000000000336613614354364023614 0ustar rootrootusing Dino.Entities; namespace Dino.Plugins.OpenPgp { public class PgpFileEncryptor : Dino.FileEncryptor, Object { StreamInteractor stream_interactor; public PgpFileEncryptor(StreamInteractor stream_interactor) { this.stream_interactor = stream_interactor; } public bool can_encrypt_file(Conversation conversation, FileTransfer file_transfer) { return conversation.encryption == Encryption.PGP; } public FileMeta encrypt_file(Conversation conversation, FileTransfer file_transfer) throws FileSendError { FileMeta file_meta = new FileMeta(); try { GPG.Key[] keys = stream_interactor.get_module(Manager.IDENTITY).get_key_fprs(conversation); uint8[] enc_content = GPGHelper.encrypt_file(file_transfer.get_file().get_path(), keys, GPG.EncryptFlags.ALWAYS_TRUST, file_transfer.file_name); file_transfer.input_stream = new MemoryInputStream.from_data(enc_content, GLib.free); file_transfer.encryption = Encryption.PGP; file_transfer.server_file_name = Xmpp.random_uuid() + ".pgp"; file_meta.size = enc_content.length; } catch (Error e) { throw new FileSendError.ENCRYPTION_FAILED("PGP file encryption error: %s".printf(e.message)); } debug("Encrypting file %s as %s", file_transfer.file_name, file_transfer.server_file_name); return file_meta; } public FileSendData? preprocess_send_file(Conversation conversation, FileTransfer file_transfer, FileSendData file_send_data, FileMeta file_meta) { HttpFileSendData? send_data = file_send_data as HttpFileSendData; if (send_data == null) return null; send_data.encrypt_message = false; return file_send_data; } } } dino-0.1.0/plugins/openpgp/src/manager.vala0000644000000000000000000001205213614354364017347 0ustar rootrootusing Gee; using Xmpp; using Xmpp; using Dino.Entities; namespace Dino.Plugins.OpenPgp { public class Manager : StreamInteractionModule, Object { public static ModuleIdentity IDENTITY = new ModuleIdentity("pgp_manager"); public string id { get { return IDENTITY.id; } } public const string MESSAGE_ENCRYPTED = "pgp"; private StreamInteractor stream_interactor; private Database db; private HashMap pgp_key_ids = new HashMap(Jid.hash_bare_func, Jid.equals_bare_func); private ReceivedMessageListener received_message_listener = new ReceivedMessageListener(); public static void start(StreamInteractor stream_interactor, Database db) { Manager m = new Manager(stream_interactor, db); stream_interactor.add_module(m); } private Manager(StreamInteractor stream_interactor, Database db) { this.stream_interactor = stream_interactor; this.db = db; stream_interactor.account_added.connect(on_account_added); stream_interactor.get_module(MessageProcessor.IDENTITY).received_pipeline.connect(received_message_listener); stream_interactor.get_module(MessageProcessor.IDENTITY).pre_message_send.connect(check_encypt); } public GPG.Key[] get_key_fprs(Conversation conversation) throws Error { Gee.List keys = new Gee.ArrayList(); keys.add(db.get_account_key(conversation.account)); if (conversation.type_ == Conversation.Type.GROUPCHAT) { Gee.List muc_jids = new Gee.ArrayList(); Gee.List? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_occupants(conversation.counterpart, conversation.account); if (occupants != null) muc_jids.add_all(occupants); Gee.List? offline_members = stream_interactor.get_module(MucManager.IDENTITY).get_offline_members(conversation.counterpart, conversation.account); if (occupants != null) muc_jids.add_all(offline_members); foreach (Jid jid in muc_jids) { string? key_id = stream_interactor.get_module(Manager.IDENTITY).get_key_id(conversation.account, jid); if (key_id != null && GPGHelper.get_keylist(key_id).size > 0 && !keys.contains(key_id)) { keys.add(key_id); } } } else { string? key_id = get_key_id(conversation.account, conversation.counterpart); if (key_id != null) { keys.add(key_id); } } GPG.Key[] gpgkeys = new GPG.Key[keys.size]; for (int i = 0; i < keys.size; i++) { try { GPG.Key key = GPGHelper.get_public_key(keys[i]); if (key != null) gpgkeys[i] = key; } catch (Error e) {} } return gpgkeys; } private void check_encypt(Entities.Message message, Xmpp.MessageStanza message_stanza, Conversation conversation) { try { if (message.encryption == Encryption.PGP) { GPG.Key[] keys = get_key_fprs(conversation); XmppStream? stream = stream_interactor.get_stream(conversation.account); if (stream != null) { bool encrypted = stream.get_module(Module.IDENTITY).encrypt(message_stanza, keys); if (!encrypted) message.marked = Entities.Message.Marked.WONTSEND; } } } catch (Error e) { message.marked = Entities.Message.Marked.WONTSEND; } } public string? get_key_id(Account account, Jid jid) { Jid search_jid = stream_interactor.get_module(MucManager.IDENTITY).is_groupchat_occupant(jid, account) ? jid : jid.bare_jid; return db.get_contact_key(search_jid); } private void on_account_added(Account account) { stream_interactor.module_manager.get_module(account, Module.IDENTITY).received_jid_key_id.connect((stream, jid, key_id) => { on_jid_key_received(account, jid, key_id); }); } private void on_jid_key_received(Account account, Jid jid, string key_id) { lock (pgp_key_ids) { if (!pgp_key_ids.has_key(jid) || pgp_key_ids[jid] != key_id) { Jid set_jid = stream_interactor.get_module(MucManager.IDENTITY).is_groupchat_occupant(jid, account) ? jid : jid.bare_jid; db.set_contact_key(set_jid, key_id); } pgp_key_ids[jid] = key_id; } } private class ReceivedMessageListener : MessageListener { public string[] after_actions_const = new string[]{ }; public override string action_group { get { return "DECRYPT"; } } public override string[] after_actions { get { return after_actions_const; } } public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) { if (MessageFlag.get_flag(stanza) != null && MessageFlag.get_flag(stanza).decrypted) { message.encryption = Encryption.PGP; } return false; } } } } dino-0.1.0/plugins/openpgp/src/plugin.vala0000644000000000000000000000407213614354364017236 0ustar rootrootusing Gee; using Dino.Entities; extern const string GETTEXT_PACKAGE; extern const string LOCALE_INSTALL_DIR; namespace Dino.Plugins.OpenPgp { public class Plugin : Plugins.RootInterface, Object { public Dino.Application app; public Database db; public HashMap modules = new HashMap(Account.hash_func, Account.equals_func); private EncryptionListEntry list_entry; private AccountSettingsEntry settings_entry; private ContactDetailsProvider contact_details_provider; public void registered(Dino.Application app) { this.app = app; this.db = new Database(Path.build_filename(Application.get_storage_dir(), "pgp.db")); this.list_entry = new EncryptionListEntry(app.stream_interactor, db); this.settings_entry = new AccountSettingsEntry(this); this.contact_details_provider = new ContactDetailsProvider(app.stream_interactor); app.plugin_registry.register_encryption_list_entry(list_entry); app.plugin_registry.register_account_settings_entry(settings_entry); app.plugin_registry.register_contact_details_entry(contact_details_provider); app.stream_interactor.module_manager.initialize_account_modules.connect(on_initialize_account_modules); Manager.start(app.stream_interactor, db); app.stream_interactor.get_module(FileManager.IDENTITY).add_file_encryptor(new PgpFileEncryptor(app.stream_interactor)); app.stream_interactor.get_module(FileManager.IDENTITY).add_file_decryptor(new PgpFileDecryptor()); JingleFileHelperRegistry.instance.add_encryption_helper(Encryption.PGP, new JingleFileEncryptionHelperTransferOnly()); internationalize(GETTEXT_PACKAGE, app.search_path_generator.get_locale_path(GETTEXT_PACKAGE, LOCALE_INSTALL_DIR)); } public void shutdown() { } private void on_initialize_account_modules(Account account, ArrayList modules) { Module module = new Module(db.get_account_key(account)); this.modules[account] = module; modules.add(module); } } } dino-0.1.0/plugins/openpgp/src/register_plugin.vala0000644000000000000000000000013713614354364021140 0ustar rootrootpublic Type register_plugin(Module module) { return typeof (Dino.Plugins.OpenPgp.Plugin); }dino-0.1.0/plugins/openpgp/src/stream_flag.vala0000644000000000000000000000105213614354364020217 0ustar rootrootusing Gee; using Xmpp; namespace Dino.Plugins.OpenPgp { public class Flag : XmppStreamFlag { public static FlagIdentity IDENTITY = new FlagIdentity(NS_URI, "pgp"); public HashMap key_ids = new HashMap(Jid.hash_bare_func, Jid.equals_bare_func); public string? get_key_id(Jid jid) { return key_ids[jid]; } public void set_key_id(Jid jid, string key) { key_ids[jid] = key; } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } } dino-0.1.0/plugins/openpgp/src/stream_module.vala0000644000000000000000000001662413614354364020606 0ustar rootrootusing GPG; using Xmpp; namespace Dino.Plugins.OpenPgp { private const string NS_URI = "jabber:x"; private const string NS_URI_ENCRYPTED = NS_URI + ":encrypted"; private const string NS_URI_SIGNED = NS_URI + ":signed"; public class Module : XmppStreamModule { public static Xmpp.ModuleIdentity IDENTITY = new Xmpp.ModuleIdentity(NS_URI, "0027_current_pgp_usage"); public signal void received_jid_key_id(XmppStream stream, Jid jid, string key_id); private string? signed_status = null; private Key? own_key = null; private ReceivedPipelineDecryptListener received_pipeline_decrypt_listener = new ReceivedPipelineDecryptListener(); public Module(string? own_key_id = null) { set_private_key_id(own_key_id); } public void set_private_key_id(string? own_key_id) { if (own_key_id != null) { try { own_key = GPGHelper.get_private_key(own_key_id); if (own_key == null) warning("Can't get PGP private key"); } catch (Error e) { } if (own_key != null) { signed_status = gpg_sign("", own_key); } } } public bool encrypt(MessageStanza message, GPG.Key[] keys) { string? enc_body = gpg_encrypt(message.body, keys); if (enc_body != null) { message.stanza.put_node(new StanzaNode.build("x", NS_URI_ENCRYPTED).add_self_xmlns().put_node(new StanzaNode.text(enc_body))); message.body = "[This message is OpenPGP encrypted (see XEP-0027)]"; Xep.ExplicitEncryption.add_encryption_tag_to_message(message, NS_URI_ENCRYPTED); return true; } return false; } public override void attach(XmppStream stream) { stream.get_module(Presence.Module.IDENTITY).received_presence.connect(on_received_presence); stream.get_module(Presence.Module.IDENTITY).pre_send_presence_stanza.connect(on_pre_send_presence_stanza); stream.get_module(MessageModule.IDENTITY).received_pipeline.connect(received_pipeline_decrypt_listener); stream.add_flag(new Flag()); } public override void detach(XmppStream stream) { stream.get_module(Presence.Module.IDENTITY).received_presence.disconnect(on_received_presence); stream.get_module(Presence.Module.IDENTITY).pre_send_presence_stanza.disconnect(on_pre_send_presence_stanza); stream.get_module(MessageModule.IDENTITY).received_pipeline.disconnect(received_pipeline_decrypt_listener); } public static void require(XmppStream stream) { if (stream.get_module(IDENTITY) == null) stream.add_module(new Module()); } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } private void on_received_presence(XmppStream stream, Presence.Stanza presence) { StanzaNode x_node = presence.stanza.get_subnode("x", NS_URI_SIGNED); if (x_node == null) return; string? sig = x_node.get_string_content(); if (sig == null) return; new Thread (null, () => { string signed_data = presence.status == null ? "" : presence.status; string? key_id = get_sign_key(sig, signed_data); if (key_id != null) { stream.get_flag(Flag.IDENTITY).set_key_id(presence.from, key_id); Idle.add(() => { received_jid_key_id(stream, presence.from, key_id); return false; }); } return null; }); } private void on_pre_send_presence_stanza(XmppStream stream, Presence.Stanza presence) { if (presence.type_ == Presence.Stanza.TYPE_AVAILABLE && signed_status != null) { presence.stanza.put_node(new StanzaNode.build("x", NS_URI_SIGNED).add_self_xmlns().put_node(new StanzaNode.text(signed_status))); } } private static string? gpg_encrypt(string plain, GPG.Key[] keys) { string encr; try { encr = GPGHelper.encrypt_armor(plain, keys, GPG.EncryptFlags.ALWAYS_TRUST); } catch (Error e) { return null; } int encryption_start = encr.index_of("\n\n") + 2; return encr.substring(encryption_start, encr.length - "\n-----END PGP MESSAGE-----".length - encryption_start); } private static string? get_sign_key(string sig, string signed_text) { string armor = "-----BEGIN PGP MESSAGE-----\n\n" + sig + "\n-----END PGP MESSAGE-----"; string? sign_key = null; try { sign_key = GPGHelper.get_sign_key(armor, signed_text); } catch (Error e) { } return sign_key; } private static string? gpg_sign(string str, Key key) { string signed; try { signed = GPGHelper.sign(str, GPG.SigMode.CLEAR, key); } catch (Error e) { return null; } int signature_start = signed.index_of("-----BEGIN PGP SIGNATURE-----"); signature_start = signed.index_of("\n\n", signature_start) + 2; return signed.substring(signature_start, signed.length - "\n-----END PGP SIGNATURE-----".length - signature_start); } } public class MessageFlag : Xmpp.MessageFlag { public const string id = "pgp"; public bool decrypted = false; public static MessageFlag? get_flag(MessageStanza message) { return (MessageFlag) message.get_flag(NS_URI, id); } public override string get_ns() { return NS_URI; } public override string get_id() { return id; } } public class ReceivedPipelineDecryptListener : StanzaListener { private const string[] after_actions_const = {"MODIFY_BODY"}; public override string action_group { get { return "ENCRYPT_BODY"; } } public override string[] after_actions { get { return after_actions_const; } } public override async bool run(XmppStream stream, MessageStanza message) { string? encrypted = get_cyphertext(message); if (encrypted != null) { MessageFlag flag = new MessageFlag(); message.add_flag(flag); string? decrypted = yield gpg_decrypt(encrypted); if (decrypted != null) { flag.decrypted = true; message.body = decrypted; } } return false; } private static async string? gpg_decrypt(string enc) { SourceFunc callback = gpg_decrypt.callback; string? res = null; new Thread (null, () => { string armor = "-----BEGIN PGP MESSAGE-----\n\n" + enc + "\n-----END PGP MESSAGE-----"; try { res = GPGHelper.decrypt(armor); } catch (Error e) { res = null; } Idle.add((owned) callback); return null; }); yield; return res; } private string? get_cyphertext(MessageStanza message) { StanzaNode? x_node = message.stanza.get_subnode("x", NS_URI_ENCRYPTED); return x_node == null ? null : x_node.get_string_content(); } } } dino-0.1.0/plugins/openpgp/src/util.vala0000644000000000000000000000312613614354364016714 0ustar rootrootusing Gtk; using Dino.Entities; using Xmpp.Util; namespace Dino.Plugins.OpenPgp { /* Adapted from OpenKeychain */ public static string markup_colorize_id(string s, bool is_fingerprint) { string markup = is_fingerprint ? "" : "0x"; for (int i = 0; i < s.length; i += 4) { string four_chars = s.substring(i, 4).down(); int raw = (int) from_hex(four_chars); uint8[] bytes = {(uint8) ((raw >> 8) & 0xff - 128), (uint8) (raw & 0xff - 128)}; Checksum checksum = new Checksum(ChecksumType.SHA1); checksum.update(bytes, bytes.length); uint8[] digest = new uint8[20]; size_t len = 20; checksum.get_digest(digest, ref len); uint8 r = digest[0]; uint8 g = digest[1]; uint8 b = digest[2]; if (r == 0 && g == 0 && b == 0) r = g = b = 1; double brightness = 0.2126 * r + 0.7152 * g + 0.0722 * b; if (brightness < 80) { double factor = 80.0 / brightness; r = uint8.min(255, (uint8) (r * factor)); g = uint8.min(255, (uint8) (g * factor)); b = uint8.min(255, (uint8) (b * factor)); } else if (brightness > 180) { double factor = 180.0 / brightness; r = (uint8) (r * factor); g = (uint8) (g * factor); b = (uint8) (b * factor); } if (i == 4 * 5) markup += "\n"; markup += @"$four_chars"; if (is_fingerprint) markup += " "; } return "" + markup + ""; } } dino-0.1.0/plugins/signal-protocol/0000755000000000000000000000000013614354364015745 5ustar rootrootdino-0.1.0/plugins/signal-protocol/CMakeLists.txt0000644000000000000000000001124513614354364020510 0ustar rootrootfind_package(GCrypt REQUIRED) find_packages(SIGNAL_PROTOCOL_PACKAGES REQUIRED Gee GLib GObject ) vala_precompile(SIGNAL_PROTOCOL_VALA_C SOURCES "src/context.vala" "src/simple_iks.vala" "src/simple_ss.vala" "src/simple_pks.vala" "src/simple_spks.vala" "src/store.vala" "src/util.vala" CUSTOM_VAPIS ${CMAKE_CURRENT_SOURCE_DIR}/vapi/signal-protocol-public.vapi ${CMAKE_CURRENT_SOURCE_DIR}/vapi/signal-protocol-native.vapi PACKAGES ${SIGNAL_PROTOCOL_PACKAGES} GENERATE_VAPI signal-protocol-vala GENERATE_HEADER signal-protocol-vala ) set(C_HEADERS_SRC "") set(C_HEADERS_TARGET "") if(NOT BUILD_LIBSIGNAL_IN_TREE) # libsignal-protocol-c has a history of breaking compatibility on the patch level # we'll have to check compatibility for every new release # distro maintainers may update this dependency after compatibility tests find_package(SignalProtocol 2.3.2 EXACT REQUIRED) else() add_subdirectory(libsignal-protocol-c EXCLUDE_FROM_ALL) set_property(TARGET curve25519 PROPERTY POSITION_INDEPENDENT_CODE ON) set_property(TARGET protobuf-c PROPERTY POSITION_INDEPENDENT_CODE ON) set_property(TARGET signal-protocol-c PROPERTY POSITION_INDEPENDENT_CODE ON) set(SIGNAL_PROTOCOL_C_HEADERS signal_protocol.h signal_protocol_types.h curve.h hkdf.h ratchet.h protocol.h session_state.h session_record.h session_pre_key.h session_builder.h session_cipher.h key_helper.h sender_key.h sender_key_state.h sender_key_record.h group_session_builder.h group_cipher.h fingerprint.h device_consistency.h ) file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/exports/signal") foreach(f ${SIGNAL_PROTOCOL_C_HEADERS}) list(APPEND C_HEADERS_SRC "${CMAKE_CURRENT_SOURCE_DIR}/libsignal-protocol-c/src/${f}") list(APPEND C_HEADERS_TARGET "${CMAKE_BINARY_DIR}/exports/signal/${f}") add_custom_command(OUTPUT "${CMAKE_BINARY_DIR}/exports/signal/${f}" COMMAND cp "${CMAKE_CURRENT_SOURCE_DIR}/libsignal-protocol-c/src/${f}" "${CMAKE_BINARY_DIR}/exports/signal/${f}" DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/libsignal-protocol-c/src/${f}" COMMENT Copy header file signal/${f} ) endforeach(f) endif() list(APPEND C_HEADERS_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/signal_helper.h") list(APPEND C_HEADERS_TARGET "${CMAKE_BINARY_DIR}/exports/signal_helper.h") add_custom_command(OUTPUT "${CMAKE_BINARY_DIR}/exports/signal_helper.h" COMMAND cp "${CMAKE_CURRENT_SOURCE_DIR}/src/signal_helper.h" "${CMAKE_BINARY_DIR}/exports/signal_helper.h" DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/signal_helper.h" COMMENT Copy header file signal_helper.h ) add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/exports/signal-protocol.vapi COMMAND cat "${CMAKE_CURRENT_SOURCE_DIR}/vapi/signal-protocol-public.vapi" "${CMAKE_BINARY_DIR}/exports/signal-protocol-vala.vapi" > "${CMAKE_BINARY_DIR}/exports/signal-protocol.vapi" DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/vapi/signal-protocol-public.vapi ${CMAKE_BINARY_DIR}/exports/signal-protocol-vala.vapi ) add_custom_target(signal-protocol-vapi DEPENDS ${CMAKE_BINARY_DIR}/exports/signal-protocol.vapi ${CMAKE_BINARY_DIR}/exports/signal-protocol-vala.h ${C_HEADERS_TARGET} ) set(CFLAGS ${VALA_CFLAGS} -I${CMAKE_CURRENT_SOURCE_DIR}/libsignal-protocol-c/src -I${CMAKE_CURRENT_SOURCE_DIR}/src) add_definitions(${CFLAGS}) add_library(signal-protocol-vala ${SIGNAL_PROTOCOL_VALA_C} ${CMAKE_CURRENT_SOURCE_DIR}/src/signal_helper.c) add_dependencies(signal-protocol-vala signal-protocol-vapi) target_link_libraries(signal-protocol-vala ${SIGNAL_PROTOCOL_PACKAGES} gcrypt signal-protocol-c m) set_property(TARGET signal-protocol-vala PROPERTY POSITION_INDEPENDENT_CODE ON) if(BUILD_TESTS) vala_precompile(SIGNAL_TEST_VALA_C SOURCES "tests/common.vala" "tests/testcase.vala" "tests/curve25519.vala" "tests/hkdf.vala" "tests/session_builder.vala" CUSTOM_VAPIS ${CMAKE_BINARY_DIR}/exports/signal-protocol-vala_internal.vapi ${CMAKE_CURRENT_SOURCE_DIR}/vapi/signal-protocol-public.vapi ${CMAKE_CURRENT_SOURCE_DIR}/vapi/signal-protocol-native.vapi PACKAGES ${SIGNAL_PROTOCOL_PACKAGES} ) set(CFLAGS ${VALA_CFLAGS} -I${CMAKE_CURRENT_BINARY_DIR}/signal-protocol) add_executable(signal-protocol-vala-test ${SIGNAL_TEST_VALA_C}) add_dependencies(signal-protocol-vala-test signal-protocol-vala) target_link_libraries(signal-protocol-vala-test signal-protocol-vala ${SIGNAL_PROTOCOL_PACKAGES}) endif(BUILD_TESTS) dino-0.1.0/plugins/signal-protocol/libsignal-protocol-c/0000755000000000000000000000000013614354364021770 5ustar rootrootdino-0.1.0/plugins/signal-protocol/src/0000755000000000000000000000000013614354364016534 5ustar rootrootdino-0.1.0/plugins/signal-protocol/src/context.vala0000644000000000000000000000761513614354364021076 0ustar rootrootnamespace Signal { public class Context { internal NativeContext native_context; private RecMutex mutex = RecMutex(); static void locking_function_lock(void* user_data) { Context ctx = (Context) user_data; ctx.mutex.lock(); } static void locking_function_unlock(void* user_data) { Context ctx = (Context) user_data; ctx.mutex.unlock(); } static void stderr_log(LogLevel level, string message, size_t len, void* user_data) { printerr(@"$level: $message\n"); } public Context(bool log = false) throws Error { throw_by_code(NativeContext.create(out native_context, this), "Error initializing native context"); throw_by_code(native_context.set_locking_functions(locking_function_lock, locking_function_unlock), "Error initializing native locking functions"); if (log) native_context.set_log_function(stderr_log); setup_crypto_provider(native_context); } public Store create_store() { return new Store(this); } public void randomize(uint8[] data) throws Error { throw_by_code(Signal.native_random(data)); } public SignedPreKeyRecord generate_signed_pre_key(IdentityKeyPair identity_key_pair, int32 id, uint64 timestamp = 0) throws Error { if (timestamp == 0) timestamp = new DateTime.now_utc().to_unix(); SignedPreKeyRecord res; throw_by_code(Protocol.KeyHelper.generate_signed_pre_key(out res, identity_key_pair, id, timestamp, native_context)); return res; } public Gee.Set generate_pre_keys(uint start, uint count) throws Error { Gee.Set res = new Gee.HashSet(); for(uint i = start; i < start+count; i++) { ECKeyPair pair = generate_key_pair(); PreKeyRecord record; throw_by_code(PreKeyRecord.create(out record, i, pair)); res.add(record); } return res; } public ECPublicKey decode_public_key(uint8[] bytes) throws Error { ECPublicKey public_key; throw_by_code(curve_decode_point(out public_key, bytes, native_context), "Error decoding public key"); return public_key; } public ECPrivateKey decode_private_key(uint8[] bytes) throws Error { ECPrivateKey private_key; throw_by_code(curve_decode_private_point(out private_key, bytes, native_context), "Error decoding private key"); return private_key; } public ECKeyPair generate_key_pair() throws Error { ECKeyPair key_pair; throw_by_code(curve_generate_key_pair(native_context, out key_pair), "Error generating key pair"); return key_pair; } public uint8[] calculate_signature(ECPrivateKey signing_key, uint8[] message) throws Error { Buffer signature; throw_by_code(Curve.calculate_signature(native_context, out signature, signing_key, message), "Error calculating signature"); return signature.data; } public SignalMessage deserialize_signal_message(uint8[] data) throws Error { SignalMessage res; throw_by_code(signal_message_deserialize(out res, data, native_context)); return res; } public SignalMessage copy_signal_message(CiphertextMessage original) throws Error { SignalMessage res; throw_by_code(signal_message_copy(out res, (SignalMessage) original, native_context)); return res; } public PreKeySignalMessage deserialize_pre_key_signal_message(uint8[] data) throws Error { PreKeySignalMessage res; throw_by_code(pre_key_signal_message_deserialize(out res, data, native_context)); return res; } public PreKeySignalMessage copy_pre_key_signal_message(CiphertextMessage original) throws Error { PreKeySignalMessage res; throw_by_code(pre_key_signal_message_copy(out res, (PreKeySignalMessage) original, native_context)); return res; } } } dino-0.1.0/plugins/signal-protocol/src/signal_helper.c0000644000000000000000000002603113614354364021516 0ustar rootroot#include #include signal_type_base* signal_type_ref_vapi(void* instance) { g_return_val_if_fail(instance != NULL, NULL); signal_type_ref(instance); return instance; } signal_type_base* signal_type_unref_vapi(void* instance) { g_return_val_if_fail(instance != NULL, NULL); signal_type_unref(instance); return NULL; } signal_protocol_address* signal_protocol_address_new(const gchar* name, int32_t device_id) { g_return_val_if_fail(name != NULL, NULL); signal_protocol_address* address = malloc(sizeof(signal_protocol_address)); address->device_id = -1; address->name = NULL; signal_protocol_address_set_name(address, name); signal_protocol_address_set_device_id(address, device_id); return address; } void signal_protocol_address_free(signal_protocol_address* ptr) { g_return_if_fail(ptr != NULL); if (ptr->name) { g_free((void*)ptr->name); } return free(ptr); } void signal_protocol_address_set_name(signal_protocol_address* self, const gchar* name) { g_return_if_fail(self != NULL); g_return_if_fail(name != NULL); gchar* n = g_malloc(strlen(name)+1); memcpy(n, name, strlen(name)); n[strlen(name)] = 0; if (self->name) { g_free((void*)self->name); } self->name = n; self->name_len = strlen(n); } gchar* signal_protocol_address_get_name(signal_protocol_address* self) { g_return_val_if_fail(self != NULL, NULL); g_return_val_if_fail(self->name != NULL, 0); gchar* res = g_malloc(sizeof(char) * (self->name_len + 1)); memcpy(res, self->name, self->name_len); res[self->name_len] = 0; return res; } int32_t signal_protocol_address_get_device_id(signal_protocol_address* self) { g_return_val_if_fail(self != NULL, -1); return self->device_id; } void signal_protocol_address_set_device_id(signal_protocol_address* self, int32_t device_id) { g_return_if_fail(self != NULL); self->device_id = device_id; } int signal_vala_randomize(uint8_t *data, size_t len) { gcry_randomize(data, len, GCRY_STRONG_RANDOM); return SG_SUCCESS; } int signal_vala_random_generator(uint8_t *data, size_t len, void *user_data) { gcry_randomize(data, len, GCRY_STRONG_RANDOM); return SG_SUCCESS; } int signal_vala_hmac_sha256_init(void **hmac_context, const uint8_t *key, size_t key_len, void *user_data) { gcry_mac_hd_t* ctx = malloc(sizeof(gcry_mac_hd_t)); if (!ctx) return SG_ERR_NOMEM; if (gcry_mac_open(ctx, GCRY_MAC_HMAC_SHA256, 0, 0)) { free(ctx); return SG_ERR_UNKNOWN; } if (gcry_mac_setkey(*ctx, key, key_len)) { free(ctx); return SG_ERR_UNKNOWN; } *hmac_context = ctx; return SG_SUCCESS; } int signal_vala_hmac_sha256_update(void *hmac_context, const uint8_t *data, size_t data_len, void *user_data) { gcry_mac_hd_t* ctx = hmac_context; if (gcry_mac_write(*ctx, data, data_len)) return SG_ERR_UNKNOWN; return SG_SUCCESS; } int signal_vala_hmac_sha256_final(void *hmac_context, signal_buffer **output, void *user_data) { size_t len = gcry_mac_get_algo_maclen(GCRY_MAC_HMAC_SHA256); uint8_t md[len]; gcry_mac_hd_t* ctx = hmac_context; if (gcry_mac_read(*ctx, md, &len)) return SG_ERR_UNKNOWN; signal_buffer *output_buffer = signal_buffer_create(md, len); if (!output_buffer) return SG_ERR_NOMEM; *output = output_buffer; return SG_SUCCESS; } void signal_vala_hmac_sha256_cleanup(void *hmac_context, void *user_data) { gcry_mac_hd_t* ctx = hmac_context; if (ctx) { gcry_mac_close(*ctx); free(ctx); } } int signal_vala_sha512_digest_init(void **digest_context, void *user_data) { gcry_md_hd_t* ctx = malloc(sizeof(gcry_mac_hd_t)); if (!ctx) return SG_ERR_NOMEM; if (gcry_md_open(ctx, GCRY_MD_SHA512, 0)) { free(ctx); return SG_ERR_UNKNOWN; } *digest_context = ctx; return SG_SUCCESS; } int signal_vala_sha512_digest_update(void *digest_context, const uint8_t *data, size_t data_len, void *user_data) { gcry_md_hd_t* ctx = digest_context; gcry_md_write(*ctx, data, data_len); return SG_SUCCESS; } int signal_vala_sha512_digest_final(void *digest_context, signal_buffer **output, void *user_data) { size_t len = gcry_md_get_algo_dlen(GCRY_MD_SHA512); gcry_md_hd_t* ctx = digest_context; uint8_t* md = gcry_md_read(*ctx, GCRY_MD_SHA512); if (!md) return SG_ERR_UNKNOWN; gcry_md_reset(*ctx); signal_buffer *output_buffer = signal_buffer_create(md, len); free(md); if (!output_buffer) return SG_ERR_NOMEM; *output = output_buffer; return SG_SUCCESS; } void signal_vala_sha512_digest_cleanup(void *digest_context, void *user_data) { gcry_md_hd_t* ctx = digest_context; if (ctx) { gcry_md_close(*ctx); free(ctx); } } const int aes_cipher(int cipher, size_t key_len, int* algo, int* mode) { switch (key_len) { case 16: *algo = GCRY_CIPHER_AES128; break; case 24: *algo = GCRY_CIPHER_AES192; break; case 32: *algo = GCRY_CIPHER_AES256; break; default: return SG_ERR_UNKNOWN; } switch (cipher) { case SG_CIPHER_AES_CBC_PKCS5: *mode = GCRY_CIPHER_MODE_CBC; break; case SG_CIPHER_AES_CTR_NOPADDING: *mode = GCRY_CIPHER_MODE_CTR; break; case SG_CIPHER_AES_GCM_NOPADDING: *mode = GCRY_CIPHER_MODE_GCM; break; default: return SG_ERR_UNKNOWN; } return SG_SUCCESS; } int signal_vala_encrypt(signal_buffer **output, int cipher, const uint8_t *key, size_t key_len, const uint8_t *iv, size_t iv_len, const uint8_t *plaintext, size_t plaintext_len, void *user_data) { int algo, mode, error_code = SG_ERR_UNKNOWN; if (aes_cipher(cipher, key_len, &algo, &mode)) return SG_ERR_INVAL; if (iv_len != 16 && iv_len != 12) return SG_ERR_INVAL; gcry_cipher_hd_t ctx = {0}; if (gcry_cipher_open(&ctx, algo, mode, 0)) return SG_ERR_NOMEM; signal_buffer* padded = 0; signal_buffer* out_buf = 0; goto no_error; error: gcry_cipher_close(ctx); if (padded != 0) { signal_buffer_bzero_free(padded); } if (out_buf != 0) { signal_buffer_free(out_buf); } return error_code; no_error: if (gcry_cipher_setkey(ctx, key, key_len)) goto error; uint8_t tag_len = 0, pad_len = 0; switch (cipher) { case SG_CIPHER_AES_CBC_PKCS5: if (gcry_cipher_setiv(ctx, iv, iv_len)) goto error; pad_len = 16 - (plaintext_len % 16); if (pad_len == 0) pad_len = 16; break; case SG_CIPHER_AES_CTR_NOPADDING: if (gcry_cipher_setctr(ctx, iv, iv_len)) goto error; break; case SG_CIPHER_AES_GCM_NOPADDING: if (gcry_cipher_setiv(ctx, iv, iv_len)) goto error; tag_len = 16; break; default: return SG_ERR_UNKNOWN; } size_t padded_len = plaintext_len + pad_len; padded = signal_buffer_alloc(padded_len); if (padded == 0) { error_code = SG_ERR_NOMEM; goto error; } memset(signal_buffer_data(padded) + plaintext_len, pad_len, pad_len); memcpy(signal_buffer_data(padded), plaintext, plaintext_len); out_buf = signal_buffer_alloc(padded_len + tag_len); if (out_buf == 0) { error_code = SG_ERR_NOMEM; goto error; } if (gcry_cipher_encrypt(ctx, signal_buffer_data(out_buf), padded_len, signal_buffer_data(padded), padded_len)) goto error; if (tag_len > 0) { if (gcry_cipher_gettag(ctx, signal_buffer_data(out_buf) + padded_len, tag_len)) goto error; } *output = out_buf; out_buf = 0; signal_buffer_bzero_free(padded); padded = 0; gcry_cipher_close(ctx); return SG_SUCCESS; } int signal_vala_decrypt(signal_buffer **output, int cipher, const uint8_t *key, size_t key_len, const uint8_t *iv, size_t iv_len, const uint8_t *ciphertext, size_t ciphertext_len, void *user_data) { int algo, mode, error_code = SG_ERR_UNKNOWN; *output = 0; if (aes_cipher(cipher, key_len, &algo, &mode)) return SG_ERR_INVAL; if (ciphertext_len == 0) return SG_ERR_INVAL; if (iv_len != 16 && iv_len != 12) return SG_ERR_INVAL; gcry_cipher_hd_t ctx = {0}; if (gcry_cipher_open(&ctx, algo, mode, 0)) return SG_ERR_NOMEM; signal_buffer* out_buf = 0; goto no_error; error: gcry_cipher_close(ctx); if (out_buf != 0) { signal_buffer_bzero_free(out_buf); } return error_code; no_error: if (gcry_cipher_setkey(ctx, key, key_len)) goto error; uint8_t tag_len = 0, pkcs_pad = FALSE; switch (cipher) { case SG_CIPHER_AES_CBC_PKCS5: if (gcry_cipher_setiv(ctx, iv, iv_len)) goto error; pkcs_pad = TRUE; break; case SG_CIPHER_AES_CTR_NOPADDING: if (gcry_cipher_setctr(ctx, iv, iv_len)) goto error; break; case SG_CIPHER_AES_GCM_NOPADDING: if (gcry_cipher_setiv(ctx, iv, iv_len)) goto error; if (ciphertext_len < 16) goto error; tag_len = 16; break; default: goto error; } size_t padded_len = ciphertext_len - tag_len; out_buf = signal_buffer_alloc(padded_len); if (out_buf == 0) { error_code = SG_ERR_NOMEM; goto error; } if (gcry_cipher_decrypt(ctx, signal_buffer_data(out_buf), signal_buffer_len(out_buf), ciphertext, padded_len)) goto error; if (tag_len > 0) { if (gcry_cipher_checktag(ctx, ciphertext + padded_len, tag_len)) goto error; } if (pkcs_pad) { uint8_t pad_len = signal_buffer_data(out_buf)[padded_len - 1]; if (pad_len > 16 || pad_len > padded_len) goto error; *output = signal_buffer_create(signal_buffer_data(out_buf), padded_len - pad_len); signal_buffer_bzero_free(out_buf); out_buf = 0; } else { *output = out_buf; out_buf = 0; } gcry_cipher_close(ctx); return SG_SUCCESS; } void setup_signal_vala_crypto_provider(signal_context *context) { gcry_check_version(NULL); signal_crypto_provider provider = { .random_func = signal_vala_random_generator, .hmac_sha256_init_func = signal_vala_hmac_sha256_init, .hmac_sha256_update_func = signal_vala_hmac_sha256_update, .hmac_sha256_final_func = signal_vala_hmac_sha256_final, .hmac_sha256_cleanup_func = signal_vala_hmac_sha256_cleanup, .sha512_digest_init_func = signal_vala_sha512_digest_init, .sha512_digest_update_func = signal_vala_sha512_digest_update, .sha512_digest_final_func = signal_vala_sha512_digest_final, .sha512_digest_cleanup_func = signal_vala_sha512_digest_cleanup, .encrypt_func = signal_vala_encrypt, .decrypt_func = signal_vala_decrypt, .user_data = 0 }; signal_context_set_crypto_provider(context, &provider); } dino-0.1.0/plugins/signal-protocol/src/signal_helper.h0000644000000000000000000000426613614354364021531 0ustar rootroot#ifndef SIGNAL_PROTOCOL_VALA_HELPER #define SIGNAL_PROTOCOL_VALA_HELPER 1 #include #include #include #define SG_CIPHER_AES_GCM_NOPADDING 1000 signal_type_base* signal_type_ref_vapi(void* what); signal_type_base* signal_type_unref_vapi(void* what); signal_protocol_address* signal_protocol_address_new(const gchar* name, int32_t device_id); void signal_protocol_address_free(signal_protocol_address* ptr); void signal_protocol_address_set_name(signal_protocol_address* self, const gchar* name); gchar* signal_protocol_address_get_name(signal_protocol_address* self); void signal_protocol_address_set_device_id(signal_protocol_address* self, int32_t device_id); int32_t signal_protocol_address_get_device_id(signal_protocol_address* self); int signal_vala_randomize(uint8_t *data, size_t len); int signal_vala_random_generator(uint8_t *data, size_t len, void *user_data); int signal_vala_hmac_sha256_init(void **hmac_context, const uint8_t *key, size_t key_len, void *user_data); int signal_vala_hmac_sha256_update(void *hmac_context, const uint8_t *data, size_t data_len, void *user_data); int signal_vala_hmac_sha256_final(void *hmac_context, signal_buffer **output, void *user_data); void signal_vala_hmac_sha256_cleanup(void *hmac_context, void *user_data); int signal_vala_sha512_digest_init(void **digest_context, void *user_data); int signal_vala_sha512_digest_update(void *digest_context, const uint8_t *data, size_t data_len, void *user_data); int signal_vala_sha512_digest_final(void *digest_context, signal_buffer **output, void *user_data); void signal_vala_sha512_digest_cleanup(void *digest_context, void *user_data); int signal_vala_encrypt(signal_buffer **output, int cipher, const uint8_t *key, size_t key_len, const uint8_t *iv, size_t iv_len, const uint8_t *plaintext, size_t plaintext_len, void *user_data); int signal_vala_decrypt(signal_buffer **output, int cipher, const uint8_t *key, size_t key_len, const uint8_t *iv, size_t iv_len, const uint8_t *ciphertext, size_t ciphertext_len, void *user_data); void setup_signal_vala_crypto_provider(signal_context *context); #endif dino-0.1.0/plugins/signal-protocol/src/simple_iks.vala0000644000000000000000000000355213614354364021545 0ustar rootrootusing Gee; namespace Signal { public class SimpleIdentityKeyStore : IdentityKeyStore { public override Bytes identity_key_private { get; set; } public override Bytes identity_key_public { get; set; } public override uint32 local_registration_id { get; set; } private Map> trusted_identities = new HashMap>(); public override void save_identity(Address address, uint8[] key) throws Error { string name = address.name; if (trusted_identities.has_key(name)) { if (trusted_identities[name].has_key(address.device_id)) { trusted_identities[name][address.device_id].key = key; trusted_identity_updated(trusted_identities[name][address.device_id]); } else { trusted_identities[name][address.device_id] = new TrustedIdentity.by_address(address, key); trusted_identity_added(trusted_identities[name][address.device_id]); } } else { trusted_identities[name] = new HashMap(); trusted_identities[name][address.device_id] = new TrustedIdentity.by_address(address, key); trusted_identity_added(trusted_identities[name][address.device_id]); } } public override bool is_trusted_identity(Address address, uint8[] key) throws Error { if (!trusted_identities.has_key(address.name)) return true; if (!trusted_identities[address.name].has_key(address.device_id)) return true; uint8[] other_key = trusted_identities[address.name][address.device_id].key; if (other_key.length != key.length) return false; for (int i = 0; i < key.length; i++) { if (other_key[i] != key[i]) return false; } return true; } } } dino-0.1.0/plugins/signal-protocol/src/simple_pks.vala0000644000000000000000000000166513614354364021557 0ustar rootrootusing Gee; namespace Signal { public class SimplePreKeyStore : PreKeyStore { private Map pre_key_map = new HashMap(); public override uint8[]? load_pre_key(uint32 pre_key_id) throws Error { if (contains_pre_key(pre_key_id)) { return pre_key_map[pre_key_id].record; } return null; } public override void store_pre_key(uint32 pre_key_id, uint8[] record) throws Error { PreKeyStore.Key key = new Key(pre_key_id, record); pre_key_map[pre_key_id] = key; pre_key_stored(key); } public override bool contains_pre_key(uint32 pre_key_id) throws Error { return pre_key_map.has_key(pre_key_id); } public override void delete_pre_key(uint32 pre_key_id) throws Error { PreKeyStore.Key key; if (pre_key_map.unset(pre_key_id, out key)) { pre_key_deleted(key); } } } }dino-0.1.0/plugins/signal-protocol/src/simple_spks.vala0000644000000000000000000000201213614354364021725 0ustar rootrootusing Gee; namespace Signal { public class SimpleSignedPreKeyStore : SignedPreKeyStore { private Map pre_key_map = new HashMap(); public override uint8[]? load_signed_pre_key(uint32 pre_key_id) throws Error { if (contains_signed_pre_key(pre_key_id)) { return pre_key_map[pre_key_id].record; } return null; } public override void store_signed_pre_key(uint32 pre_key_id, uint8[] record) throws Error { SignedPreKeyStore.Key key = new Key(pre_key_id, record); pre_key_map[pre_key_id] = key; signed_pre_key_stored(key); } public override bool contains_signed_pre_key(uint32 pre_key_id) throws Error { return pre_key_map.has_key(pre_key_id); } public override void delete_signed_pre_key(uint32 pre_key_id) throws Error { SignedPreKeyStore.Key key; if (pre_key_map.unset(pre_key_id, out key)) { signed_pre_key_deleted(key); } } } }dino-0.1.0/plugins/signal-protocol/src/simple_ss.vala0000644000000000000000000000530013614354364021375 0ustar rootrootusing Gee; namespace Signal { public class SimpleSessionStore : SessionStore { private Map> session_map = new HashMap>(); public override uint8[]? load_session(Address address) throws Error { if (session_map.has_key(address.name)) { foreach (SessionStore.Session session in session_map[address.name]) { if (session.device_id == address.device_id) return session.record; } } return null; } public override IntList get_sub_device_sessions(string name) throws Error { IntList res = new IntList(); if (session_map.has_key(name)) { foreach (SessionStore.Session session in session_map[name]) { res.add(session.device_id); } } return res; } public override void store_session(Address address, uint8[] record) throws Error { if (contains_session(address)) { delete_session(address); } if (!session_map.has_key(address.name)) { session_map[address.name] = new ArrayList(); } SessionStore.Session session = new Session() { name = address.name, device_id = address.device_id, record = record }; session_map[address.name].add(session); session_stored(session); } public override bool contains_session(Address address) throws Error { if (!session_map.has_key(address.name)) return false; foreach (SessionStore.Session session in session_map[address.name]) { if (session.device_id == address.device_id) return true; } return false; } public override void delete_session(Address address) throws Error { if (!session_map.has_key(address.name)) throw_by_code(ErrorCode.UNKNOWN, "No session found"); foreach (SessionStore.Session session in session_map[address.name]) { if (session.device_id == address.device_id) { session_map[address.name].remove(session); if (session_map[address.name].size == 0) { session_map.unset(address.name); } session_removed(session); return; } } } public override void delete_all_sessions(string name) throws Error { if (session_map.has_key(name)) { foreach (SessionStore.Session session in session_map[name]) { session_map[name].remove(session); if (session_map[name].size == 0) { session_map.unset(name); } session_removed(session); } } } } }dino-0.1.0/plugins/signal-protocol/src/store.vala0000644000000000000000000003516113614354364020543 0ustar rootrootnamespace Signal { public abstract class IdentityKeyStore : Object { public abstract Bytes identity_key_private { get; set; } public abstract Bytes identity_key_public { get; set; } public abstract uint32 local_registration_id { get; set; } public signal void trusted_identity_added(TrustedIdentity id); public signal void trusted_identity_updated(TrustedIdentity id); public abstract void save_identity(Address address, uint8[] key) throws Error ; public abstract bool is_trusted_identity(Address address, uint8[] key) throws Error ; public class TrustedIdentity { public uint8[] key { get; set; } public string name { get; private set; } public int device_id { get; private set; } public TrustedIdentity(string name, int device_id, uint8[] key) { this.key = key; this.name = name; this.device_id = device_id; } public TrustedIdentity.by_address(Address address, uint8[] key) { this(address.name, address.device_id, key); } } } public abstract class SessionStore : Object { public signal void session_stored(Session session); public signal void session_removed(Session session); public abstract uint8[]? load_session(Address address) throws Error ; public abstract IntList get_sub_device_sessions(string name) throws Error ; public abstract void store_session(Address address, uint8[] record) throws Error ; public abstract bool contains_session(Address address) throws Error ; public abstract void delete_session(Address address) throws Error ; public abstract void delete_all_sessions(string name) throws Error ; public class Session { public string name; public int device_id; public uint8[] record; } } public abstract class PreKeyStore : Object { public signal void pre_key_stored(Key key); public signal void pre_key_deleted(Key key); public abstract uint8[]? load_pre_key(uint32 pre_key_id) throws Error ; public abstract void store_pre_key(uint32 pre_key_id, uint8[] record) throws Error ; public abstract bool contains_pre_key(uint32 pre_key_id) throws Error ; public abstract void delete_pre_key(uint32 pre_key_id) throws Error ; public class Key { public uint32 key_id { get; private set; } public uint8[] record { get; private set; } public Key(uint32 key_id, uint8[] record) { this.key_id = key_id; this.record = record; } } } public abstract class SignedPreKeyStore : Object { public signal void signed_pre_key_stored(Key key); public signal void signed_pre_key_deleted(Key key); public abstract uint8[]? load_signed_pre_key(uint32 pre_key_id) throws Error ; public abstract void store_signed_pre_key(uint32 pre_key_id, uint8[] record) throws Error ; public abstract bool contains_signed_pre_key(uint32 pre_key_id) throws Error ; public abstract void delete_signed_pre_key(uint32 pre_key_id) throws Error ; public class Key { public uint32 key_id { get; private set; } public uint8[] record { get; private set; } public Key(uint32 key_id, uint8[] record) { this.key_id = key_id; this.record = record; } } } public class Store : Object { public Context context { get; private set; } public IdentityKeyStore identity_key_store { get; set; default = new SimpleIdentityKeyStore(); } public SessionStore session_store { get; set; default = new SimpleSessionStore(); } public PreKeyStore pre_key_store { get; set; default = new SimplePreKeyStore(); } public SignedPreKeyStore signed_pre_key_store { get; set; default = new SimpleSignedPreKeyStore(); } public uint32 local_registration_id { get { return identity_key_store.local_registration_id; } } internal NativeStoreContext native_context {get { return native_store_context_; }} private NativeStoreContext native_store_context_; static int iks_get_identity_key_pair(out Buffer public_data, out Buffer private_data, void* user_data) { Store store = (Store) user_data; public_data = new Buffer.from(store.identity_key_store.identity_key_public.get_data()); private_data = new Buffer.from(store.identity_key_store.identity_key_private.get_data()); return 0; } static int iks_get_local_registration_id(void* user_data, out uint32 registration_id) { Store store = (Store) user_data; registration_id = store.identity_key_store.local_registration_id; return 0; } static int iks_save_identity(Address address, uint8[] key, void* user_data) { Store store = (Store) user_data; return catch_to_code(() => { store.identity_key_store.save_identity(address, key); return 0; }); } static int iks_is_trusted_identity(Address address, uint8[] key, void* user_data) { Store store = (Store) user_data; return catch_to_code(() => { return store.identity_key_store.is_trusted_identity(address, key) ? 1 : 0; }); } static void iks_destroy_func(void* user_data) { } static int ss_load_session_func(out Buffer? record, out Buffer? user_record, Address address, void* user_data) { Store store = (Store) user_data; user_record = null; // No support for user_record uint8[]? res = null; try { res = store.session_store.load_session(address); } catch (Error e) { record = null; return e.code; } if (res == null) { record = null; return 0; } record = new Buffer.from((!)res); if (record == null) return ErrorCode.NOMEM; return 1; } static int ss_get_sub_device_sessions_func(out IntList? sessions, char[] name, void* user_data) { Store store = (Store) user_data; try { sessions = store.session_store.get_sub_device_sessions(carr_to_string(name)); } catch (Error e) { sessions = null; return e.code; } return 0; } static int ss_store_session_func(Address address, uint8[] record, uint8[] user_record, void* user_data) { // Ignoring user_record Store store = (Store) user_data; return catch_to_code(() => { store.session_store.store_session(address, record); return 0; }); } static int ss_contains_session_func(Address address, void* user_data) { Store store = (Store) user_data; return catch_to_code(() => { return store.session_store.contains_session(address) ? 1 : 0; }); } static int ss_delete_session_func(Address address, void* user_data) { Store store = (Store) user_data; return catch_to_code(() => { store.session_store.delete_session(address); return 0; }); } static int ss_delete_all_sessions_func(char[] name, void* user_data) { Store store = (Store) user_data; return catch_to_code(() => { store.session_store.delete_all_sessions(carr_to_string(name)); return 0; }); } static void ss_destroy_func(void* user_data) { } static int pks_load_pre_key(out Buffer? record, uint32 pre_key_id, void* user_data) { Store store = (Store) user_data; uint8[]? res = null; try { res = store.pre_key_store.load_pre_key(pre_key_id); } catch (Error e) { record = null; return e.code; } if (res == null) { record = new Buffer(0); return 0; } record = new Buffer.from((!)res); if (record == null) return ErrorCode.NOMEM; return 1; } static int pks_store_pre_key(uint32 pre_key_id, uint8[] record, void* user_data) { Store store = (Store) user_data; return catch_to_code(() => { store.pre_key_store.store_pre_key(pre_key_id, record); return 0; }); } static int pks_contains_pre_key(uint32 pre_key_id, void* user_data) { Store store = (Store) user_data; return catch_to_code(() => { return store.pre_key_store.contains_pre_key(pre_key_id) ? 1 : 0; }); } static int pks_remove_pre_key(uint32 pre_key_id, void* user_data) { Store store = (Store) user_data; return catch_to_code(() => { store.pre_key_store.delete_pre_key(pre_key_id); return 0; }); } static void pks_destroy_func(void* user_data) { } static int spks_load_signed_pre_key(out Buffer? record, uint32 pre_key_id, void* user_data) { Store store = (Store) user_data; uint8[]? res = null; try { res = store.signed_pre_key_store.load_signed_pre_key(pre_key_id); } catch (Error e) { record = null; return e.code; } if (res == null) { record = new Buffer(0); return 0; } record = new Buffer.from((!)res); if (record == null) return ErrorCode.NOMEM; return 1; } static int spks_store_signed_pre_key(uint32 pre_key_id, uint8[] record, void* user_data) { Store store = (Store) user_data; return catch_to_code(() => { store.signed_pre_key_store.store_signed_pre_key(pre_key_id, record); return 0; }); } static int spks_contains_signed_pre_key(uint32 pre_key_id, void* user_data) { Store store = (Store) user_data; return catch_to_code(() => { return store.signed_pre_key_store.contains_signed_pre_key(pre_key_id) ? 1 : 0; }); } static int spks_remove_signed_pre_key(uint32 pre_key_id, void* user_data) { Store store = (Store) user_data; return catch_to_code(() => { store.signed_pre_key_store.delete_signed_pre_key(pre_key_id); return 0; }); } static void spks_destroy_func(void* user_data) { } internal Store(Context context) { this.context = context; NativeStoreContext.create(out native_store_context_, context.native_context); NativeIdentityKeyStore iks = NativeIdentityKeyStore() { get_identity_key_pair = iks_get_identity_key_pair, get_local_registration_id = iks_get_local_registration_id, save_identity = iks_save_identity, is_trusted_identity = iks_is_trusted_identity, destroy_func = iks_destroy_func, user_data = this }; native_context.set_identity_key_store(iks); NativeSessionStore ss = NativeSessionStore() { load_session_func = ss_load_session_func, get_sub_device_sessions_func = ss_get_sub_device_sessions_func, store_session_func = ss_store_session_func, contains_session_func = ss_contains_session_func, delete_session_func = ss_delete_session_func, delete_all_sessions_func = ss_delete_all_sessions_func, destroy_func = ss_destroy_func, user_data = this }; native_context.set_session_store(ss); NativePreKeyStore pks = NativePreKeyStore() { load_pre_key = pks_load_pre_key, store_pre_key = pks_store_pre_key, contains_pre_key = pks_contains_pre_key, remove_pre_key = pks_remove_pre_key, destroy_func = pks_destroy_func, user_data = this }; native_context.set_pre_key_store(pks); NativeSignedPreKeyStore spks = NativeSignedPreKeyStore() { load_signed_pre_key = spks_load_signed_pre_key, store_signed_pre_key = spks_store_signed_pre_key, contains_signed_pre_key = spks_contains_signed_pre_key, remove_signed_pre_key = spks_remove_signed_pre_key, destroy_func = spks_destroy_func, user_data = this }; native_context.set_signed_pre_key_store(spks); } public SessionBuilder create_session_builder(Address other) throws Error { SessionBuilder builder; throw_by_code(session_builder_create(out builder, native_context, other, context.native_context), "Error creating session builder"); return builder; } public SessionCipher create_session_cipher(Address other) throws Error { SessionCipher cipher; throw_by_code(session_cipher_create(out cipher, native_context, other, context.native_context)); return cipher; } public IdentityKeyPair identity_key_pair { owned get { IdentityKeyPair pair; Protocol.Identity.get_key_pair(native_context, out pair); return pair; } } public bool is_trusted_identity(Address address, ECPublicKey key) throws Error { return throw_by_code(Protocol.Identity.is_trusted_identity(native_context, address, key)) == 1; } public void save_identity(Address address, ECPublicKey key) throws Error { throw_by_code(Protocol.Identity.save_identity(native_context, address, key)); } public bool contains_session(Address other) throws Error { return throw_by_code(Protocol.Session.contains_session(native_context, other)) == 1; } public void delete_session(Address address) throws Error { throw_by_code(Protocol.Session.delete_session(native_context, address)); } public SessionRecord load_session(Address other) throws Error { SessionRecord record; throw_by_code(Protocol.Session.load_session(native_context, out record, other)); return record; } public bool contains_pre_key(uint32 pre_key_id) throws Error { return throw_by_code(Protocol.PreKey.contains_key(native_context, pre_key_id)) == 1; } public void store_pre_key(PreKeyRecord record) throws Error { throw_by_code(Protocol.PreKey.store_key(native_context, record)); } public PreKeyRecord load_pre_key(uint32 pre_key_id) throws Error { PreKeyRecord res; throw_by_code(Protocol.PreKey.load_key(native_context, out res, pre_key_id)); return res; } public bool contains_signed_pre_key(uint32 pre_key_id) throws Error { return throw_by_code(Protocol.SignedPreKey.contains_key(native_context, pre_key_id)) == 1; } public void store_signed_pre_key(SignedPreKeyRecord record) throws Error { throw_by_code(Protocol.SignedPreKey.store_key(native_context, record)); } public SignedPreKeyRecord load_signed_pre_key(uint32 pre_key_id) throws Error { SignedPreKeyRecord res; throw_by_code(Protocol.SignedPreKey.load_key(native_context, out res, pre_key_id)); return res; } } } dino-0.1.0/plugins/signal-protocol/src/util.vala0000644000000000000000000000313513614354364020360 0ustar rootrootnamespace Signal { public ECPublicKey generate_public_key(ECPrivateKey private_key) throws Error { ECPublicKey public_key; throw_by_code(ECPublicKey.generate(out public_key, private_key), "Error generating public key"); return public_key; } public uint8[] calculate_agreement(ECPublicKey public_key, ECPrivateKey private_key) throws Error { uint8[] res; int len = Curve.calculate_agreement(out res, public_key, private_key); throw_by_code(len, "Error calculating agreement"); res.length = len; return res; } public bool verify_signature(ECPublicKey signing_key, uint8[] message, uint8[] signature) throws Error { return throw_by_code(Curve.verify_signature(signing_key, message, signature)) == 1; } public PreKeyBundle create_pre_key_bundle(uint32 registration_id, int device_id, uint32 pre_key_id, ECPublicKey? pre_key_public, uint32 signed_pre_key_id, ECPublicKey? signed_pre_key_public, uint8[]? signed_pre_key_signature, ECPublicKey? identity_key) throws Error { PreKeyBundle res; throw_by_code(PreKeyBundle.create(out res, registration_id, device_id, pre_key_id, pre_key_public, signed_pre_key_id, signed_pre_key_public, signed_pre_key_signature, identity_key), "Error creating PreKeyBundle"); return res; } internal string carr_to_string(char[] carr) { char[] nu = new char[carr.length + 1]; Memory.copy(nu, carr, carr.length); return (string) nu; } internal delegate int CodeErroringFunc() throws Error; internal int catch_to_code(CodeErroringFunc func) { try { return func(); } catch (Error e) { return e.code; } } }dino-0.1.0/plugins/signal-protocol/tests/0000755000000000000000000000000013614354364017107 5ustar rootrootdino-0.1.0/plugins/signal-protocol/tests/common.vala0000644000000000000000000000551313614354364021250 0ustar rootrootnamespace Signal.Test { int main(string[] args) { GLib.Test.init(ref args); GLib.Test.set_nonfatal_assertions(); TestSuite.get_root().add_suite(new Curve25519().get_suite()); TestSuite.get_root().add_suite(new SessionBuilderTest().get_suite()); TestSuite.get_root().add_suite(new HKDF().get_suite()); return GLib.Test.run(); } Store setup_test_store_context(Context global_context) { Store store = global_context.create_store(); try { store.identity_key_store.local_registration_id = (Random.next_int() % 16380) + 1; ECKeyPair key_pair = global_context.generate_key_pair(); store.identity_key_store.identity_key_private = new Bytes(key_pair.private.serialize()); store.identity_key_store.identity_key_public = new Bytes(key_pair.public.serialize()); } catch (Error e) { fail_if_reached(); } return store; } ECPublicKey? create_test_ec_public_key(Context context) { try { return context.generate_key_pair().public; } catch (Error e) { fail_if_reached(); return null; } } bool fail_if(bool exp, string? reason = null) { if (exp) { if (reason != null) GLib.Test.message(reason); GLib.Test.fail(); return true; } return false; } void fail_if_reached(string? reason = null) { fail_if(true, reason); } delegate void ErrorFunc() throws Error; void fail_if_not_error_code(ErrorFunc func, int expectedCode, string? reason = null) { try { func(); fail_if_reached(@"$(reason + ": " ?? "")no error thrown"); } catch (Error e) { fail_if_not_eq_int(e.code, expectedCode, @"$(reason + ": " ?? "")caught unexpected error"); } } bool fail_if_not(bool exp, string? reason = null) { return fail_if(!exp, reason); } bool fail_if_eq_int(int left, int right, string? reason = null) { return fail_if(left == right, @"$(reason + ": " ?? "")$left == $right"); } bool fail_if_not_eq_int(int left, int right, string? reason = null) { return fail_if_not(left == right, @"$(reason + ": " ?? "")$left != $right"); } bool fail_if_not_eq_str(string left, string right, string? reason = null) { return fail_if_not(left == right, @"$(reason + ": " ?? "")$left != $right"); } bool fail_if_not_eq_uint8_arr(uint8[] left, uint8[] right, string? reason = null) { if (fail_if_not_eq_int(left.length, right.length, @"$(reason + ": " ?? "")array length not equal")) return true; return fail_if_not_eq_str(Base64.encode(left), Base64.encode(right), reason); } bool fail_if_not_zero_int(int zero, string? reason = null) { return fail_if_not_eq_int(zero, 0, reason); } bool fail_if_zero_int(int zero, string? reason = null) { return fail_if_eq_int(zero, 0, reason); } bool fail_if_null(void* what, string? reason = null) { return fail_if(what == null || (size_t)what == 0, reason); } } dino-0.1.0/plugins/signal-protocol/tests/curve25519.vala0000644000000000000000000002012313614354364021504 0ustar rootrootnamespace Signal.Test { class Curve25519 : Gee.TestCase { public Curve25519() { base("Curve25519"); add_test("agreement", test_curve25519_agreement); add_test("generate_public", test_curve25519_generate_public); add_test("random_agreements", test_curve25519_random_agreements); add_test("signature", test_curve25519_signature); } private Context global_context; public override void set_up() { try { global_context = new Context(); } catch (Error e) { fail_if_reached(); } } public override void tear_down() { global_context = null; } void test_curve25519_agreement() { try { uint8[] alicePublic = { 0x05, 0x1b, 0xb7, 0x59, 0x66, 0xf2, 0xe9, 0x3a, 0x36, 0x91, 0xdf, 0xff, 0x94, 0x2b, 0xb2, 0xa4, 0x66, 0xa1, 0xc0, 0x8b, 0x8d, 0x78, 0xca, 0x3f, 0x4d, 0x6d, 0xf8, 0xb8, 0xbf, 0xa2, 0xe4, 0xee, 0x28}; uint8[] alicePrivate = { 0xc8, 0x06, 0x43, 0x9d, 0xc9, 0xd2, 0xc4, 0x76, 0xff, 0xed, 0x8f, 0x25, 0x80, 0xc0, 0x88, 0x8d, 0x58, 0xab, 0x40, 0x6b, 0xf7, 0xae, 0x36, 0x98, 0x87, 0x90, 0x21, 0xb9, 0x6b, 0xb4, 0xbf, 0x59}; uint8[] bobPublic = { 0x05, 0x65, 0x36, 0x14, 0x99, 0x3d, 0x2b, 0x15, 0xee, 0x9e, 0x5f, 0xd3, 0xd8, 0x6c, 0xe7, 0x19, 0xef, 0x4e, 0xc1, 0xda, 0xae, 0x18, 0x86, 0xa8, 0x7b, 0x3f, 0x5f, 0xa9, 0x56, 0x5a, 0x27, 0xa2, 0x2f}; uint8[] bobPrivate = { 0xb0, 0x3b, 0x34, 0xc3, 0x3a, 0x1c, 0x44, 0xf2, 0x25, 0xb6, 0x62, 0xd2, 0xbf, 0x48, 0x59, 0xb8, 0x13, 0x54, 0x11, 0xfa, 0x7b, 0x03, 0x86, 0xd4, 0x5f, 0xb7, 0x5d, 0xc5, 0xb9, 0x1b, 0x44, 0x66}; uint8[] shared = { 0x32, 0x5f, 0x23, 0x93, 0x28, 0x94, 0x1c, 0xed, 0x6e, 0x67, 0x3b, 0x86, 0xba, 0x41, 0x01, 0x74, 0x48, 0xe9, 0x9b, 0x64, 0x9a, 0x9c, 0x38, 0x06, 0xc1, 0xdd, 0x7c, 0xa4, 0xc4, 0x77, 0xe6, 0x29}; ECPublicKey alice_public_key = global_context.decode_public_key(alicePublic); ECPrivateKey alice_private_key = global_context.decode_private_key(alicePrivate); ECPublicKey bob_public_key = global_context.decode_public_key(bobPublic); ECPrivateKey bob_private_key = global_context.decode_private_key(bobPrivate); uint8[] shared_one = calculate_agreement(alice_public_key, bob_private_key); uint8[] shared_two = calculate_agreement(bob_public_key, alice_private_key); fail_if_not_eq_int(shared_one.length, 32); fail_if_not_eq_int(shared_two.length, 32); fail_if_not_eq_uint8_arr(shared, shared_one); fail_if_not_eq_uint8_arr(shared_one, shared_two); } catch (Error e) { fail_if_reached(); } } void test_curve25519_generate_public() { try { uint8[] alicePublic = { 0x05, 0x1b, 0xb7, 0x59, 0x66, 0xf2, 0xe9, 0x3a, 0x36, 0x91, 0xdf, 0xff, 0x94, 0x2b, 0xb2, 0xa4, 0x66, 0xa1, 0xc0, 0x8b, 0x8d, 0x78, 0xca, 0x3f, 0x4d, 0x6d, 0xf8, 0xb8, 0xbf, 0xa2, 0xe4, 0xee, 0x28}; uint8[] alicePrivate = { 0xc8, 0x06, 0x43, 0x9d, 0xc9, 0xd2, 0xc4, 0x76, 0xff, 0xed, 0x8f, 0x25, 0x80, 0xc0, 0x88, 0x8d, 0x58, 0xab, 0x40, 0x6b, 0xf7, 0xae, 0x36, 0x98, 0x87, 0x90, 0x21, 0xb9, 0x6b, 0xb4, 0xbf, 0x59}; ECPrivateKey alice_private_key = global_context.decode_private_key(alicePrivate); ECPublicKey alice_expected_public_key = global_context.decode_public_key(alicePublic); ECPublicKey alice_public_key = generate_public_key(alice_private_key); fail_if_not_zero_int(alice_expected_public_key.compare(alice_public_key)); } catch (Error e) { fail_if_reached(); } } void test_curve25519_random_agreements() { try { ECKeyPair alice_key_pair = null; ECPublicKey alice_public_key = null; ECPrivateKey alice_private_key = null; ECKeyPair bob_key_pair = null; ECPublicKey bob_public_key = null; ECPrivateKey bob_private_key = null; uint8[] shared_alice = null; uint8[] shared_bob = null; for (int i = 0; i < 50; i++) { fail_if_null(alice_key_pair = global_context.generate_key_pair()); fail_if_null(alice_public_key = alice_key_pair.public); fail_if_null(alice_private_key = alice_key_pair.private); fail_if_null(bob_key_pair = global_context.generate_key_pair()); fail_if_null(bob_public_key = bob_key_pair.public); fail_if_null(bob_private_key = bob_key_pair.private); shared_alice = calculate_agreement(bob_public_key, alice_private_key); fail_if_not_eq_int(shared_alice.length, 32); shared_bob = calculate_agreement(alice_public_key, bob_private_key); fail_if_not_eq_int(shared_bob.length, 32); fail_if_not_eq_uint8_arr(shared_alice, shared_bob); } } catch (Error e) { fail_if_reached(); } } void test_curve25519_signature() { try { uint8[] aliceIdentityPrivate = { 0xc0, 0x97, 0x24, 0x84, 0x12, 0xe5, 0x8b, 0xf0, 0x5d, 0xf4, 0x87, 0x96, 0x82, 0x05, 0x13, 0x27, 0x94, 0x17, 0x8e, 0x36, 0x76, 0x37, 0xf5, 0x81, 0x8f, 0x81, 0xe0, 0xe6, 0xce, 0x73, 0xe8, 0x65}; uint8[] aliceIdentityPublic = { 0x05, 0xab, 0x7e, 0x71, 0x7d, 0x4a, 0x16, 0x3b, 0x7d, 0x9a, 0x1d, 0x80, 0x71, 0xdf, 0xe9, 0xdc, 0xf8, 0xcd, 0xcd, 0x1c, 0xea, 0x33, 0x39, 0xb6, 0x35, 0x6b, 0xe8, 0x4d, 0x88, 0x7e, 0x32, 0x2c, 0x64}; uint8[] aliceEphemeralPublic = { 0x05, 0xed, 0xce, 0x9d, 0x9c, 0x41, 0x5c, 0xa7, 0x8c, 0xb7, 0x25, 0x2e, 0x72, 0xc2, 0xc4, 0xa5, 0x54, 0xd3, 0xeb, 0x29, 0x48, 0x5a, 0x0e, 0x1d, 0x50, 0x31, 0x18, 0xd1, 0xa8, 0x2d, 0x99, 0xfb, 0x4a}; uint8[] aliceSignature = { 0x5d, 0xe8, 0x8c, 0xa9, 0xa8, 0x9b, 0x4a, 0x11, 0x5d, 0xa7, 0x91, 0x09, 0xc6, 0x7c, 0x9c, 0x74, 0x64, 0xa3, 0xe4, 0x18, 0x02, 0x74, 0xf1, 0xcb, 0x8c, 0x63, 0xc2, 0x98, 0x4e, 0x28, 0x6d, 0xfb, 0xed, 0xe8, 0x2d, 0xeb, 0x9d, 0xcd, 0x9f, 0xae, 0x0b, 0xfb, 0xb8, 0x21, 0x56, 0x9b, 0x3d, 0x90, 0x01, 0xbd, 0x81, 0x30, 0xcd, 0x11, 0xd4, 0x86, 0xce, 0xf0, 0x47, 0xbd, 0x60, 0xb8, 0x6e, 0x88}; global_context.decode_private_key(aliceIdentityPrivate); global_context.decode_public_key(aliceEphemeralPublic); ECPublicKey alice_public_key = global_context.decode_public_key(aliceIdentityPublic); fail_if(!verify_signature(alice_public_key, aliceEphemeralPublic, aliceSignature), "signature verification failed"); uint8[] modifiedSignature = new uint8[aliceSignature.length]; for (int i = 0; i < aliceSignature.length; i++) { Memory.copy(modifiedSignature, aliceSignature, aliceSignature.length); modifiedSignature[i] ^= 0x01; fail_if(verify_signature(alice_public_key, aliceEphemeralPublic, modifiedSignature), "invalid signature verification succeeded"); } } catch (Error e) { fail_if_reached(); } } } }dino-0.1.0/plugins/signal-protocol/tests/hkdf.vala0000644000000000000000000000326313614354364020674 0ustar rootrootnamespace Signal.Test { class HKDF : Gee.TestCase { public HKDF() { base("HKDF"); add_test("vector_v3", test_hkdf_vector_v3); } private Context global_context; public override void set_up() { try { global_context = new Context(); } catch (Error e) { fail_if_reached(); } } public override void tear_down() { global_context = null; } public void test_hkdf_vector_v3() { uint8[] ikm = { 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b}; uint8[] salt = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c}; uint8[] info = { 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9}; uint8[] okm = { 0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, 0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36, 0x2f, 0x2a, 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c, 0x5d, 0xb0, 0x2d, 0x56, 0xec, 0xc4, 0xc5, 0xbf, 0x34, 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18, 0x58, 0x65}; NativeHkdfContext context = null; fail_if_not_zero_int(NativeHkdfContext.create(out context, 3, global_context.native_context)); uint8[] output = null; int result = (int) context.derive_secrets(out output, ikm, salt, info, 42); fail_if_not_eq_int(result, okm.length); output.length = result; fail_if_not_eq_uint8_arr(output, okm); } } }dino-0.1.0/plugins/signal-protocol/tests/session_builder.vala0000644000000000000000000005070613614354364023155 0ustar rootrootnamespace Signal.Test { class SessionBuilderTest : Gee.TestCase { Address alice_address; Address bob_address; public SessionBuilderTest() { base("SessionBuilder"); add_test("basic_pre_key_v2", test_basic_pre_key_v2); add_test("basic_pre_key_v3", test_basic_pre_key_v3); add_test("bad_signed_pre_key_signature", test_bad_signed_pre_key_signature); add_test("repeat_bundle_message_v2", test_repeat_bundle_message_v2); } private Context global_context; public override void set_up() { try { global_context = new Context(); alice_address = new Address("+14151111111", 1); bob_address = new Address("+14152222222", 1); } catch (Error e) { fail_if_reached(@"Unexpected error: $(e.message)"); } } public override void tear_down() { global_context = null; alice_address = null; bob_address = null; } void test_basic_pre_key_v2() { try { /* Create Alice's data store and session builder */ Store alice_store = setup_test_store_context(global_context); SessionBuilder alice_session_builder = alice_store.create_session_builder(bob_address); /* Create Bob's data store and pre key bundle */ Store bob_store = setup_test_store_context(global_context); uint32 bob_local_registration_id = bob_store.local_registration_id; IdentityKeyPair bob_identity_key_pair = bob_store.identity_key_pair; ECKeyPair bob_pre_key_pair = global_context.generate_key_pair(); PreKeyBundle bob_pre_key = create_pre_key_bundle(bob_local_registration_id, 1, 31337, bob_pre_key_pair.public, 0, null, null, bob_identity_key_pair.public); /* * Have Alice process Bob's pre key bundle, which should fail due to a * missing unsigned pre key. */ fail_if_not_error_code(() => alice_session_builder.process_pre_key_bundle(bob_pre_key), ErrorCode.INVALID_KEY); } catch(Error e) { fail_if_reached(@"Unexpected error: $(e.message)"); } } void test_basic_pre_key_v3() { try { /* Create Alice's data store and session builder */ Store alice_store = setup_test_store_context(global_context); SessionBuilder alice_session_builder = alice_store.create_session_builder(bob_address); /* Create Bob's data store and pre key bundle */ Store bob_store = setup_test_store_context(global_context); uint32 bob_local_registration_id = bob_store.local_registration_id; ECKeyPair bob_pre_key_pair = global_context.generate_key_pair(); ECKeyPair bob_signed_pre_key_pair = global_context.generate_key_pair(); IdentityKeyPair bob_identity_key_pair = bob_store.identity_key_pair; uint8[] bob_signed_pre_key_signature = global_context.calculate_signature(bob_identity_key_pair.private, bob_signed_pre_key_pair.public.serialize()); PreKeyBundle bob_pre_key = create_pre_key_bundle(bob_local_registration_id, 1, 31337, bob_pre_key_pair.public, 22, bob_signed_pre_key_pair.public, bob_signed_pre_key_signature, bob_identity_key_pair.public); /* Have Alice process Bob's pre key bundle */ alice_session_builder.process_pre_key_bundle(bob_pre_key); /* Check that we can load the session state and verify its version */ fail_if_not(alice_store.contains_session(bob_address)); SessionRecord loaded_record = alice_store.load_session(bob_address); fail_if_not_eq_int((int)loaded_record.state.session_version, 3); /* Encrypt an outgoing message to send to Bob */ string original_message = "L'homme est condamnรฉ ร  รชtre libre"; SessionCipher alice_session_cipher = alice_store.create_session_cipher(bob_address); CiphertextMessage outgoing_message = alice_session_cipher.encrypt(original_message.data); fail_if_not_eq_int(outgoing_message.type, CiphertextType.PREKEY); /* Convert to an incoming message for Bob */ PreKeySignalMessage incoming_message = global_context.deserialize_pre_key_signal_message(outgoing_message.serialized); /* Save the pre key and signed pre key in Bob's data store */ PreKeyRecord bob_pre_key_record; throw_by_code(PreKeyRecord.create(out bob_pre_key_record, bob_pre_key.pre_key_id, bob_pre_key_pair)); bob_store.store_pre_key(bob_pre_key_record); SignedPreKeyRecord bob_signed_pre_key_record; throw_by_code(SignedPreKeyRecord.create(out bob_signed_pre_key_record, 22, new DateTime.now_utc().to_unix(), bob_signed_pre_key_pair, bob_signed_pre_key_signature)); bob_store.store_signed_pre_key(bob_signed_pre_key_record); /* Create Bob's session cipher and decrypt the message from Alice */ SessionCipher bob_session_cipher = bob_store.create_session_cipher(alice_address); /* Prepare the data for the callback test */ //int callback_context = 1234; //bob_session_cipher.user_data = //bob_session_cipher.decryption_callback = uint8[] plaintext = bob_session_cipher.decrypt_pre_key_signal_message(incoming_message); /* Clean up callback data */ bob_session_cipher.user_data = null; bob_session_cipher.decryption_callback = null; /* Verify Bob's session state and the decrypted message */ fail_if_not(bob_store.contains_session(alice_address)); SessionRecord alice_recipient_session_record = bob_store.load_session(alice_address); SessionState alice_recipient_session_state = alice_recipient_session_record.state; fail_if_not_eq_int((int)alice_recipient_session_state.session_version, 3); fail_if_null(alice_recipient_session_state.alice_base_key); fail_if_not_eq_uint8_arr(original_message.data, plaintext); /* Have Bob send a reply to Alice */ CiphertextMessage bob_outgoing_message = bob_session_cipher.encrypt(original_message.data); fail_if_not_eq_int(bob_outgoing_message.type, CiphertextType.SIGNAL); /* Verify that Alice can decrypt it */ SignalMessage bob_outgoing_message_copy = global_context.copy_signal_message(bob_outgoing_message); uint8[] alice_plaintext = alice_session_cipher.decrypt_signal_message(bob_outgoing_message_copy); fail_if_not_eq_uint8_arr(original_message.data, alice_plaintext); GLib.Test.message("Pre-interaction tests complete"); /* Interaction tests */ run_interaction(alice_store, bob_store); /* Cleanup state from previous tests that we need to replace */ alice_store = null; bob_pre_key_pair = null; bob_signed_pre_key_pair = null; bob_identity_key_pair = null; bob_signed_pre_key_signature = null; bob_pre_key_record = null; bob_signed_pre_key_record = null; /* Create Alice's new session data */ alice_store = setup_test_store_context(global_context); alice_session_builder = alice_store.create_session_builder(bob_address); alice_session_cipher = alice_store.create_session_cipher(bob_address); /* Create Bob's new pre key bundle */ bob_pre_key_pair = global_context.generate_key_pair(); bob_signed_pre_key_pair = global_context.generate_key_pair(); bob_identity_key_pair = bob_store.identity_key_pair; bob_signed_pre_key_signature = global_context.calculate_signature(bob_identity_key_pair.private, bob_signed_pre_key_pair.public.serialize()); bob_pre_key = create_pre_key_bundle(bob_local_registration_id, 1, 31338, bob_pre_key_pair.public, 23, bob_signed_pre_key_pair.public, bob_signed_pre_key_signature, bob_identity_key_pair.public); /* Save the new pre key and signed pre key in Bob's data store */ throw_by_code(PreKeyRecord.create(out bob_pre_key_record, bob_pre_key.pre_key_id, bob_pre_key_pair)); bob_store.store_pre_key(bob_pre_key_record); throw_by_code(SignedPreKeyRecord.create(out bob_signed_pre_key_record, 23, new DateTime.now_utc().to_unix(), bob_signed_pre_key_pair, bob_signed_pre_key_signature)); bob_store.store_signed_pre_key(bob_signed_pre_key_record); /* Have Alice process Bob's pre key bundle */ alice_session_builder.process_pre_key_bundle(bob_pre_key); /* Have Alice encrypt a message for Bob */ outgoing_message = alice_session_cipher.encrypt(original_message.data); fail_if_not_eq_int(outgoing_message.type, CiphertextType.PREKEY); /* Have Bob try to decrypt the message */ PreKeySignalMessage outgoing_message_copy = global_context.copy_pre_key_signal_message(outgoing_message); /* The decrypt should fail with a specific error */ fail_if_not_error_code(() => bob_session_cipher.decrypt_pre_key_signal_message(outgoing_message_copy), ErrorCode.UNTRUSTED_IDENTITY); outgoing_message_copy = global_context.copy_pre_key_signal_message(outgoing_message); /* Save the identity key to Bob's store */ bob_store.save_identity(alice_address, outgoing_message_copy.identity_key); /* Try the decrypt again, this time it should succeed */ outgoing_message_copy = global_context.copy_pre_key_signal_message(outgoing_message); plaintext = bob_session_cipher.decrypt_pre_key_signal_message(outgoing_message_copy); fail_if_not_eq_uint8_arr(original_message.data, plaintext); /* Create a new pre key for Bob */ ECPublicKey test_public_key = create_test_ec_public_key(global_context); IdentityKeyPair alice_identity_key_pair = alice_store.identity_key_pair; bob_pre_key = create_pre_key_bundle(bob_local_registration_id, 1, 31337, test_public_key, 23, bob_signed_pre_key_pair.public, bob_signed_pre_key_signature, alice_identity_key_pair.public); /* Have Alice process Bob's new pre key bundle, which should fail */ fail_if_not_error_code(() => alice_session_builder.process_pre_key_bundle(bob_pre_key), ErrorCode.UNTRUSTED_IDENTITY); GLib.Test.message("Post-interaction tests complete"); } catch(Error e) { fail_if_reached(@"Unexpected error: $(e.message)"); } } void test_bad_signed_pre_key_signature() { try { /* Create Alice's data store and session builder */ Store alice_store = setup_test_store_context(global_context); SessionBuilder alice_session_builder = alice_store.create_session_builder(bob_address); /* Create Bob's data store */ Store bob_store = setup_test_store_context(global_context); /* Create Bob's regular and signed pre key pairs */ ECKeyPair bob_pre_key_pair = global_context.generate_key_pair(); ECKeyPair bob_signed_pre_key_pair = global_context.generate_key_pair(); /* Create Bob's signed pre key signature */ IdentityKeyPair bob_identity_key_pair = bob_store.identity_key_pair; uint8[] bob_signed_pre_key_signature = global_context.calculate_signature(bob_identity_key_pair.private, bob_signed_pre_key_pair.public.serialize()); for (int i = 0; i < bob_signed_pre_key_signature.length * 8; i++) { uint8[] modified_signature = bob_signed_pre_key_signature[0:bob_signed_pre_key_signature.length]; /* Intentionally corrupt the signature data */ modified_signature[i/8] ^= (1 << ((uint8)i % 8)); /* Create a pre key bundle */ PreKeyBundle bob_pre_key = create_pre_key_bundle(bob_store.local_registration_id,1,31137,bob_pre_key_pair.public,22,bob_signed_pre_key_pair.public,modified_signature,bob_identity_key_pair.public); /* Process the bundle and make sure we fail with an invalid key error */ fail_if_not_error_code(() => alice_session_builder.process_pre_key_bundle(bob_pre_key), ErrorCode.INVALID_KEY); } /* Create a correct pre key bundle */ PreKeyBundle bob_pre_key = create_pre_key_bundle(bob_store.local_registration_id,1,31137,bob_pre_key_pair.public,22,bob_signed_pre_key_pair.public,bob_signed_pre_key_signature,bob_identity_key_pair.public); /* Process the bundle and make sure we do not fail */ alice_session_builder.process_pre_key_bundle(bob_pre_key); } catch(Error e) { fail_if_reached(@"Unexpected error: $(e.message)"); } } void test_repeat_bundle_message_v2() { try { /* Create Alice's data store and session builder */ Store alice_store = setup_test_store_context(global_context); SessionBuilder alice_session_builder = alice_store.create_session_builder(bob_address); /* Create Bob's data store and pre key bundle */ Store bob_store = setup_test_store_context(global_context); ECKeyPair bob_pre_key_pair = global_context.generate_key_pair(); ECKeyPair bob_signed_pre_key_pair = global_context.generate_key_pair(); uint8[] bob_signed_pre_key_signature = global_context.calculate_signature(bob_store.identity_key_pair.private, bob_signed_pre_key_pair.public.serialize()); PreKeyBundle bob_pre_key = create_pre_key_bundle(bob_store.local_registration_id,1,31337,bob_pre_key_pair.public,0,null,null,bob_store.identity_key_pair.public); /* Add Bob's pre keys to Bob's data store */ PreKeyRecord bob_pre_key_record; throw_by_code(PreKeyRecord.create(out bob_pre_key_record, bob_pre_key.pre_key_id, bob_pre_key_pair)); bob_store.store_pre_key(bob_pre_key_record); SignedPreKeyRecord bob_signed_pre_key_record; throw_by_code(SignedPreKeyRecord.create(out bob_signed_pre_key_record, 22, new DateTime.now_utc().to_unix(), bob_signed_pre_key_pair, bob_signed_pre_key_signature)); bob_store.store_signed_pre_key(bob_signed_pre_key_record); /* * Have Alice process Bob's pre key bundle, which should fail due to a * missing signed pre key. */ fail_if_not_error_code(() => alice_session_builder.process_pre_key_bundle(bob_pre_key), ErrorCode.INVALID_KEY); } catch(Error e) { fail_if_reached(@"Unexpected error: $(e.message)"); } } class Holder { public uint8[] data { get; private set; } public Holder(uint8[] data) { this.data = data; } } void run_interaction(Store alice_store, Store bob_store) throws Error { /* Create the session ciphers */ SessionCipher alice_session_cipher = alice_store.create_session_cipher(bob_address); SessionCipher bob_session_cipher = bob_store.create_session_cipher(alice_address); /* Create a test message */ string original_message = "smert ze smert"; /* Simulate Alice sending a message to Bob */ CiphertextMessage alice_message = alice_session_cipher.encrypt(original_message.data); fail_if_not_eq_int(alice_message.type, CiphertextType.SIGNAL); SignalMessage alice_message_copy = global_context.copy_signal_message(alice_message); uint8[] plaintext = bob_session_cipher.decrypt_signal_message(alice_message_copy); fail_if_not_eq_uint8_arr(original_message.data, plaintext); GLib.Test.message("Interaction complete: Alice -> Bob"); /* Simulate Bob sending a message to Alice */ CiphertextMessage bob_message = bob_session_cipher.encrypt(original_message.data); fail_if_not_eq_int(alice_message.type, CiphertextType.SIGNAL); SignalMessage bob_message_copy = global_context.copy_signal_message(bob_message); plaintext = alice_session_cipher.decrypt_signal_message(bob_message_copy); fail_if_not_eq_uint8_arr(original_message.data, plaintext); GLib.Test.message("Interaction complete: Bob -> Alice"); /* Looping Alice -> Bob */ for (int i = 0; i < 10; i++) { uint8[] looping_message = create_looping_message(i); CiphertextMessage alice_looping_message = alice_session_cipher.encrypt(looping_message); SignalMessage alice_looping_message_copy = global_context.copy_signal_message(alice_looping_message); uint8[] looping_plaintext = bob_session_cipher.decrypt_signal_message(alice_looping_message_copy); fail_if_not_eq_uint8_arr(looping_message, looping_plaintext); } GLib.Test.message("Interaction complete: Alice -> Bob (looping)"); /* Looping Bob -> Alice */ for (int i = 0; i < 10; i++) { uint8[] looping_message = create_looping_message(i); CiphertextMessage bob_looping_message = bob_session_cipher.encrypt(looping_message); SignalMessage bob_looping_message_copy = global_context.copy_signal_message(bob_looping_message); uint8[] looping_plaintext = alice_session_cipher.decrypt_signal_message(bob_looping_message_copy); fail_if_not_eq_uint8_arr(looping_message, looping_plaintext); } GLib.Test.message("Interaction complete: Bob -> Alice (looping)"); /* Generate a shuffled list of encrypted messages for later use */ Holder[] alice_ooo_plaintext = new Holder[10]; Holder[] alice_ooo_ciphertext = new Holder[10]; for (int i = 0; i < 10; i++) { alice_ooo_plaintext[i] = new Holder(create_looping_message(i)); alice_ooo_ciphertext[i] = new Holder(alice_session_cipher.encrypt(alice_ooo_plaintext[i].data).serialized); } for (int i = 0; i < 10; i++) { uint32 s = Random.next_int() % 10; Holder tmp = alice_ooo_plaintext[s]; alice_ooo_plaintext[s] = alice_ooo_plaintext[i]; alice_ooo_plaintext[i] = tmp; tmp = alice_ooo_ciphertext[s]; alice_ooo_ciphertext[s] = alice_ooo_ciphertext[i]; alice_ooo_ciphertext[i] = tmp; } GLib.Test.message("Shuffled Alice->Bob messages created"); /* Looping Alice -> Bob (repeated) */ for (int i = 0; i < 10; i++) { uint8[] looping_message = create_looping_message(i); CiphertextMessage alice_looping_message = alice_session_cipher.encrypt(looping_message); SignalMessage alice_looping_message_copy = global_context.copy_signal_message(alice_looping_message); uint8[] looping_plaintext = bob_session_cipher.decrypt_signal_message(alice_looping_message_copy); fail_if_not_eq_uint8_arr(looping_message, looping_plaintext); } GLib.Test.message("Interaction complete: Alice -> Bob (looping, repeated)"); /* Looping Bob -> Alice (repeated) */ for (int i = 0; i < 10; i++) { uint8[] looping_message = create_looping_message(i); CiphertextMessage bob_looping_message = bob_session_cipher.encrypt(looping_message); SignalMessage bob_looping_message_copy = global_context.copy_signal_message(bob_looping_message); uint8[] looping_plaintext = alice_session_cipher.decrypt_signal_message(bob_looping_message_copy); fail_if_not_eq_uint8_arr(looping_message, looping_plaintext); } GLib.Test.message("Interaction complete: Bob -> Alice (looping, repeated)"); /* Shuffled Alice -> Bob */ for (int i = 0; i < 10; i++) { SignalMessage ooo_message_deserialized = global_context.deserialize_signal_message(alice_ooo_ciphertext[i].data); uint8[] ooo_plaintext = bob_session_cipher.decrypt_signal_message(ooo_message_deserialized); fail_if_not_eq_uint8_arr(alice_ooo_plaintext[i].data, ooo_plaintext); } GLib.Test.message("Interaction complete: Alice -> Bob (shuffled)"); } uint8[] create_looping_message(int index) { return (@"You can only desire based on what you know: $index").data; } /* uint8[] create_looping_message_short(int index) { return ("What do we mean by saying that existence precedes essence? " + "We mean that man first of all exists, encounters himself, " + @"surges up in the world--and defines himself aftward. $index").data; } */ } } dino-0.1.0/plugins/signal-protocol/tests/testcase.vala0000644000000000000000000000450213614354364021570 0ustar rootroot/* testcase.vala * * Copyright (C) 2009 Julien Peeters * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Author: * Julien Peeters */ public abstract class Gee.TestCase : Object { private GLib.TestSuite suite; private Adaptor[] adaptors = new Adaptor[0]; public delegate void TestMethod (); protected TestCase (string name) { this.suite = new GLib.TestSuite (name); } public void add_test (string name, owned TestMethod test) { var adaptor = new Adaptor (name, (owned)test, this); this.adaptors += adaptor; this.suite.add (new GLib.TestCase (adaptor.name, adaptor.set_up, adaptor.run, adaptor.tear_down )); } public virtual void set_up () { } public virtual void tear_down () { } public GLib.TestSuite get_suite () { return this.suite; } private class Adaptor { [CCode (notify = false)] public string name { get; private set; } private TestMethod test; private TestCase test_case; public Adaptor (string name, owned TestMethod test, TestCase test_case) { this.name = name; this.test = (owned)test; this.test_case = test_case; } public void set_up (void* fixture) { this.test_case.set_up (); } public void run (void* fixture) { this.test (); } public void tear_down (void* fixture) { this.test_case.tear_down (); } } }dino-0.1.0/plugins/signal-protocol/vapi/0000755000000000000000000000000013614354364016704 5ustar rootrootdino-0.1.0/plugins/signal-protocol/vapi/signal-protocol-native.vapi0000644000000000000000000004063013614354364024170 0ustar rootrootnamespace Signal { [Compact] [CCode (cname = "signal_context", cprefix="signal_context_", free_function="signal_context_destroy", cheader_filename = "signal/signal_protocol.h")] public class NativeContext { public static int create(out NativeContext context, void* user_data); public int set_crypto_provider(NativeCryptoProvider crypto_provider); public int set_locking_functions(LockingFunc lock, LockingFunc unlock); public int set_log_function(LogFunc log); } [CCode (has_target = false)] public delegate void LockingFunc(void* user_data); [CCode (has_target = false)] public delegate void LogFunc(LogLevel level, string message, size_t len, void* user_data); [Compact] [CCode (cname = "signal_crypto_provider", cheader_filename = "signal/signal_protocol.h")] public struct NativeCryptoProvider { public RandomFunc random_func; public HmacSha256Init hmac_sha256_init_func; public HmacSha256Update hmac_sha256_update_func; public HmacSha256Final hmac_sha256_final_func; public HmacSha256Cleanup hmac_sha256_cleanup_func; public Sha512DigestInit sha512_digest_init_func; public Sha512DigestUpdate sha512_digest_update_func; public Sha512DigestFinal sha512_digest_final_func; public Sha512DigestCleanup sha512_digest_cleanup_func; public CryptFunc encrypt_func; public CryptFunc decrypt_func; public void* user_data; } [CCode (has_target = false)] public delegate int RandomFunc(uint8[] data, void* user_data); [CCode (has_target = false)] public delegate int HmacSha256Init(out void* hmac_context, uint8[] key, void* user_data); [CCode (has_target = false)] public delegate int HmacSha256Update(void* hmac_context, uint8[] data, void* user_data); [CCode (has_target = false)] public delegate int HmacSha256Final(void* hmac_context, out Buffer buffer, void* user_data); [CCode (has_target = false)] public delegate int HmacSha256Cleanup(void* hmac_context, void* user_data); [CCode (has_target = false)] public delegate int Sha512DigestInit(out void* digest_context, void* user_data); [CCode (has_target = false)] public delegate int Sha512DigestUpdate(void* digest_context, uint8[] data, void* user_data); [CCode (has_target = false)] public delegate int Sha512DigestFinal(void* digest_context, out Buffer buffer, void* user_data); [CCode (has_target = false)] public delegate int Sha512DigestCleanup(void* digest_context, void* user_data); [CCode (has_target = false)] public delegate int CryptFunc(out Buffer output, Cipher cipher, uint8[] key, uint8[] iv, uint8[] content, void* user_data); [Compact] [CCode (cname = "signal_protocol_session_store", cheader_filename = "signal/signal_protocol.h")] public struct NativeSessionStore { public LoadSessionFunc load_session_func; public GetSubDeviceSessionsFunc get_sub_device_sessions_func; public StoreSessionFunc store_session_func; public ContainsSessionFunc contains_session_func; public DeleteSessionFunc delete_session_func; public DeleteAllSessionsFunc delete_all_sessions_func; public DestroyFunc destroy_func; public void* user_data; } [CCode (has_target = false)] public delegate int LoadSessionFunc(out Buffer record, out Buffer user_record, Address address, void* user_data); [CCode (has_target = false)] public delegate int GetSubDeviceSessionsFunc(out IntList sessions, [CCode (array_length_type = "size_t")] char[] name, void* user_data); [CCode (has_target = false)] public delegate int StoreSessionFunc(Address address, [CCode (array_length_type = "size_t")] uint8[] record, [CCode (array_length_type = "size_t")] uint8[] user_record, void* user_data); [CCode (has_target = false)] public delegate int ContainsSessionFunc(Address address, void* user_data); [CCode (has_target = false)] public delegate int DeleteSessionFunc(Address address, void* user_data); [CCode (has_target = false)] public delegate int DeleteAllSessionsFunc([CCode (array_length_type = "size_t")] char[] name, void* user_data); [Compact] [CCode (cname = "signal_protocol_identity_key_store", cheader_filename = "signal/signal_protocol.h")] public struct NativeIdentityKeyStore { GetIdentityKeyPairFunc get_identity_key_pair; GetLocalRegistrationIdFunc get_local_registration_id; SaveIdentityFunc save_identity; IsTrustedIdentityFunc is_trusted_identity; DestroyFunc destroy_func; void* user_data; } [CCode (has_target = false)] public delegate int GetIdentityKeyPairFunc(out Buffer public_data, out Buffer private_data, void* user_data); [CCode (has_target = false)] public delegate int GetLocalRegistrationIdFunc(void* user_data, out uint32 registration_id); [CCode (has_target = false)] public delegate int SaveIdentityFunc(Address address, [CCode (array_length_type = "size_t")] uint8[] key, void* user_data); [CCode (has_target = false)] public delegate int IsTrustedIdentityFunc(Address address, [CCode (array_length_type = "size_t")] uint8[] key, void* user_data); [Compact] [CCode (cname = "signal_protocol_pre_key_store", cheader_filename = "signal/signal_protocol.h")] public struct NativePreKeyStore { LoadPreKeyFunc load_pre_key; StorePreKeyFunc store_pre_key; ContainsPreKeyFunc contains_pre_key; RemovePreKeyFunc remove_pre_key; DestroyFunc destroy_func; void* user_data; } [CCode (has_target = false)] public delegate int LoadPreKeyFunc(out Buffer record, uint32 pre_key_id, void* user_data); [CCode (has_target = false)] public delegate int StorePreKeyFunc(uint32 pre_key_id, [CCode (array_length_type = "size_t")] uint8[] record, void* user_data); [CCode (has_target = false)] public delegate int ContainsPreKeyFunc(uint32 pre_key_id, void* user_data); [CCode (has_target = false)] public delegate int RemovePreKeyFunc(uint32 pre_key_id, void* user_data); [Compact] [CCode (cname = "signal_protocol_signed_pre_key_store", cheader_filename = "signal/signal_protocol.h")] public struct NativeSignedPreKeyStore { LoadPreKeyFunc load_signed_pre_key; StorePreKeyFunc store_signed_pre_key; ContainsPreKeyFunc contains_signed_pre_key; RemovePreKeyFunc remove_signed_pre_key; DestroyFunc destroy_func; void* user_data; } [Compact] [CCode (cname = "signal_protocol_sender_key_store")] public struct NativeSenderKeyStore { StoreSenderKeyFunc store_sender_key; LoadSenderKeyFunc load_sender_key; DestroyFunc destroy_func; void* user_data; } [CCode (has_target = false)] public delegate int StoreSenderKeyFunc(SenderKeyName sender_key_name, [CCode (array_length_type = "size_t")] uint8[] record, [CCode (array_length_type = "size_t")] uint8[] user_record, void* user_data); [CCode (has_target = false)] public delegate int LoadSenderKeyFunc(out Buffer record, out Buffer user_record, SenderKeyName sender_key_name, void* user_data); [CCode (has_target = false)] public delegate void DestroyFunc(void* user_data); [Compact] [CCode (cname = "signal_protocol_store_context", cprefix = "signal_protocol_store_context_", free_function="signal_protocol_store_context_destroy", cheader_filename = "signal/signal_protocol.h")] public class NativeStoreContext { public static int create(out NativeStoreContext context, NativeContext global_context); public int set_session_store(NativeSessionStore store); public int set_pre_key_store(NativePreKeyStore store); public int set_signed_pre_key_store(NativeSignedPreKeyStore store); public int set_identity_key_store(NativeIdentityKeyStore store); public int set_sender_key_store(NativeSenderKeyStore store); } [CCode (cheader_filename = "signal/signal_protocol.h")] namespace Protocol { /** * Interface to the pre-key store. * These functions will use the callbacks in the provided * signal_protocol_store_context instance and operate in terms of higher level * library data structures. */ [CCode (cprefix = "signal_protocol_pre_key_")] namespace PreKey { public int load_key(NativeStoreContext context, out PreKeyRecord pre_key, uint32 pre_key_id); public int store_key(NativeStoreContext context, PreKeyRecord pre_key); public int contains_key(NativeStoreContext context, uint32 pre_key_id); public int remove_key(NativeStoreContext context, uint32 pre_key_id); } [CCode (cprefix = "signal_protocol_signed_pre_key_")] namespace SignedPreKey { public int load_key(NativeStoreContext context, out SignedPreKeyRecord pre_key, uint32 pre_key_id); public int store_key(NativeStoreContext context, SignedPreKeyRecord pre_key); public int contains_key(NativeStoreContext context, uint32 pre_key_id); public int remove_key(NativeStoreContext context, uint32 pre_key_id); } /** * Interface to the session store. * These functions will use the callbacks in the provided * signal_protocol_store_context instance and operate in terms of higher level * library data structures. */ [CCode (cprefix = "signal_protocol_session_")] namespace Session { public int load_session(NativeStoreContext context, out SessionRecord record, Address address); public int get_sub_device_sessions(NativeStoreContext context, out IntList sessions, char[] name); public int store_session(NativeStoreContext context, Address address, SessionRecord record); public int contains_session(NativeStoreContext context, Address address); public int delete_session(NativeStoreContext context, Address address); public int delete_all_sessions(NativeStoreContext context, char[] name); } namespace Identity { public int get_key_pair(NativeStoreContext store_context, out IdentityKeyPair key_pair); public int get_local_registration_id(NativeStoreContext store_context, out uint32 registration_id); public int save_identity(NativeStoreContext store_context, Address address, ECPublicKey identity_key); public int is_trusted_identity(NativeStoreContext store_context, Address address, ECPublicKey identity_key); } [CCode (cheader_filename = "signal/key_helper.h", cprefix = "signal_protocol_key_helper_")] namespace KeyHelper { [Compact] [CCode (cname = "signal_protocol_key_helper_pre_key_list_node", cprefix = "signal_protocol_key_helper_key_list_", free_function="signal_protocol_key_helper_key_list_free")] public class PreKeyListNode { public PreKeyRecord element(); public PreKeyListNode next(); } public int generate_identity_key_pair(out IdentityKeyPair key_pair, NativeContext global_context); public int generate_registration_id(out int32 registration_id, int extended_range, NativeContext global_context); public int get_random_sequence(out int value, int max, NativeContext global_context); public int generate_pre_keys(out PreKeyListNode head, uint start, uint count, NativeContext global_context); public int generate_last_resort_pre_key(out PreKeyRecord pre_key, NativeContext global_context); public int generate_signed_pre_key(out SignedPreKeyRecord signed_pre_key, IdentityKeyPair identity_key_pair, uint32 signed_pre_key_id, uint64 timestamp, NativeContext global_context); public int generate_sender_signing_key(out ECKeyPair key_pair, NativeContext global_context); public int generate_sender_key(out Buffer key_buffer, NativeContext global_context); public int generate_sender_key_id(out int32 key_id, NativeContext global_context); } } [CCode (cheader_filename = "signal/curve.h")] namespace Curve { [CCode (cname = "curve_calculate_agreement")] public int calculate_agreement([CCode (array_length = false)] out uint8[] shared_key_data, ECPublicKey public_key, ECPrivateKey private_key); [CCode (cname = "curve_calculate_signature")] public int calculate_signature(NativeContext context, out Buffer signature, ECPrivateKey signing_key, uint8[] message); [CCode (cname = "curve_verify_signature")] public int verify_signature(ECPublicKey signing_key, uint8[] message, uint8[] signature); } [CCode (cname = "session_builder_create", cheader_filename = "signal/session_builder.h")] public static int session_builder_create(out SessionBuilder builder, NativeStoreContext store, Address remote_address, NativeContext global_context); [CCode (cname = "session_cipher_create", cheader_filename = "signal/session_cipher.h")] public static int session_cipher_create(out SessionCipher cipher, NativeStoreContext store, Address remote_address, NativeContext global_context); [CCode (cname = "pre_key_signal_message_deserialize", cheader_filename = "signal/protocol.h")] public static int pre_key_signal_message_deserialize(out PreKeySignalMessage message, uint8[] data, NativeContext global_context); [CCode (cname = "pre_key_signal_message_copy", cheader_filename = "signal/protocol.h")] public static int pre_key_signal_message_copy(out PreKeySignalMessage message, PreKeySignalMessage other_message, NativeContext global_context); [CCode (cname = "signal_message_create", cheader_filename = "signal/protocol.h")] public static int signal_message_create(out SignalMessage message, uint8 message_version, uint8[] mac_key, ECPublicKey sender_ratchet_key, uint32 counter, uint32 previous_counter, uint8[] ciphertext, ECPublicKey sender_identity_key, ECPublicKey receiver_identity_key, NativeContext global_context); [CCode (cname = "signal_message_deserialize", cheader_filename = "signal/protocol.h")] public static int signal_message_deserialize(out SignalMessage message, uint8[] data, NativeContext global_context); [CCode (cname = "signal_message_copy", cheader_filename = "signal/protocol.h")] public static int signal_message_copy(out SignalMessage message, SignalMessage other_message, NativeContext global_context); [CCode (cname = "curve_generate_key_pair", cheader_filename = "signal/curve.h")] public static int curve_generate_key_pair(NativeContext context, out ECKeyPair key_pair); [CCode (cname = "curve_decode_private_point", cheader_filename = "signal/curve.h")] public static int curve_decode_private_point(out ECPrivateKey public_key, uint8[] key, NativeContext global_context); [CCode (cname = "curve_decode_point", cheader_filename = "signal/curve.h")] public static int curve_decode_point(out ECPublicKey public_key, uint8[] key, NativeContext global_context); [CCode (cname = "curve_generate_private_key", cheader_filename = "signal/curve.h")] public static int curve_generate_private_key(NativeContext context, out ECPrivateKey private_key); [CCode (cname = "ratchet_identity_key_pair_deserialize", cheader_filename = "signal/ratchet.h")] public static int ratchet_identity_key_pair_deserialize(out IdentityKeyPair key_pair, uint8[] data, NativeContext global_context); [CCode (cname = "session_signed_pre_key_deserialize", cheader_filename = "signal/signed_pre_key.h")] public static int session_signed_pre_key_deserialize(out SignedPreKeyRecord pre_key, uint8[] data, NativeContext global_context); [Compact] [CCode (cname = "hkdf_context", cprefix = "hkdf_", free_function = "hkdf_destroy", cheader_filename = "signal/hkdf.h")] public class NativeHkdfContext { public static int create(out NativeHkdfContext context, int message_version, NativeContext global_context); public int compare(NativeHkdfContext other); public ssize_t derive_secrets([CCode (array_length = false)] out uint8[] output, uint8[] input_key_material, uint8[] salt, uint8[] info, size_t output_len); } [CCode (cname = "setup_signal_vala_crypto_provider", cheader_filename = "signal_helper.h")] public static void setup_crypto_provider(NativeContext context); [CCode (cname = "signal_vala_randomize", cheader_filename = "signal_helper.h")] public static int native_random(uint8[] data); } dino-0.1.0/plugins/signal-protocol/vapi/signal-protocol-public.vapi0000644000000000000000000004172213614354364024163 0ustar rootrootnamespace Signal { [CCode (cname = "int", cprefix = "SG_ERR_", cheader_filename = "signal/signal_protocol.h", has_type_id = false)] public enum ErrorCode { [CCode (cname = "SG_SUCCESS")] SUCCESS, NOMEM, INVAL, UNKNOWN, DUPLICATE_MESSAGE, INVALID_KEY, INVALID_KEY_ID, INVALID_MAC, INVALID_MESSAGE, INVALID_VERSION, LEGACY_MESSAGE, NO_SESSION, STALE_KEY_EXCHANGE, UNTRUSTED_IDENTITY, VRF_SIG_VERIF_FAILED, INVALID_PROTO_BUF, FP_VERSION_MISMATCH, FP_IDENT_MISMATCH; } [CCode (cname = "SG_ERR_MINIMUM", cheader_filename = "signal/signal_protocol.h")] public const int MIN_ERROR_CODE; [CCode (cname = "int", cprefix = "SG_LOG_", cheader_filename = "signal/signal_protocol.h", has_type_id = false)] public enum LogLevel { ERROR, WARNING, NOTICE, INFO, DEBUG } [CCode (cname = "signal_throw_gerror_by_code_", cheader_filename = "signal/signal_protocol.h")] private int throw_by_code(int code, string? message = null) throws GLib.Error { if (code < 0 && code > MIN_ERROR_CODE) { throw new GLib.Error(-1, code, "%s: %s", message ?? "Signal error", ((ErrorCode)code).to_string()); } return code; } [CCode (cname = "int", cprefix = "SG_CIPHER_", cheader_filename = "signal/signal_protocol.h", has_type_id = false)] public enum Cipher { AES_CTR_NOPADDING, AES_CBC_PKCS5, AES_GCM_NOPADDING } [Compact] [CCode (cname = "signal_type_base", ref_function="signal_type_ref_vapi", unref_function="signal_type_unref_vapi", cheader_filename="signal/signal_protocol_types.h,signal_helper.h")] public class TypeBase { } [Compact] [CCode (cname = "signal_buffer", cheader_filename = "signal/signal_protocol_types.h", free_function="signal_buffer_free")] public class Buffer { [CCode (cname = "signal_buffer_alloc")] public Buffer(size_t len); [CCode (cname = "signal_buffer_create")] public Buffer.from(uint8[] data); public Buffer copy(); public Buffer append(uint8[] data); public int compare(Buffer other); public uint8 get(int i) { return data[i]; } public void set(int i, uint8 val) { data[i] = val; } public uint8[] data { get { int x = (int)len(); unowned uint8[] res = _data(); res.length = x; return res; } } [CCode (array_length = false, cname = "signal_buffer_data")] private unowned uint8[] _data(); private size_t len(); } [Compact] [CCode (cname = "signal_int_list", cheader_filename = "signal/signal_protocol_types.h", free_function="signal_int_list_free")] public class IntList { [CCode (cname = "signal_int_list_alloc")] public IntList(); [CCode (cname = "signal_int_list_push_back")] public int add(int value); public uint size { [CCode (cname = "signal_int_list_size")] get; } [CCode (cname = "signal_int_list_at")] public int get(uint index); } [Compact] [CCode (cname = "session_builder", cprefix = "session_builder_", free_function="session_builder_free", cheader_filename = "signal/session_builder.h")] public class SessionBuilder { [CCode (cname = "session_builder_process_pre_key_bundle")] private int process_pre_key_bundle_(PreKeyBundle pre_key_bundle); [CCode (cname = "session_builder_process_pre_key_bundle_")] public void process_pre_key_bundle(PreKeyBundle pre_key_bundle) throws GLib.Error { throw_by_code(process_pre_key_bundle_(pre_key_bundle)); } } [Compact] [CCode (cname = "session_pre_key_bundle", cprefix = "session_pre_key_bundle_", cheader_filename = "signal/session_pre_key.h")] public class PreKeyBundle : TypeBase { public static int create(out PreKeyBundle bundle, uint32 registration_id, int device_id, uint32 pre_key_id, ECPublicKey? pre_key_public, uint32 signed_pre_key_id, ECPublicKey? signed_pre_key_public, uint8[]? signed_pre_key_signature, ECPublicKey? identity_key); public uint32 registration_id { get; } public int device_id { get; } public uint32 pre_key_id { get; } public ECPublicKey pre_key { owned get; } public uint32 signed_pre_key_id { get; } public ECPublicKey signed_pre_key { owned get; } public Buffer signed_pre_key_signature { owned get; } public ECPublicKey identity_key { owned get; } } [Compact] [CCode (cname = "session_pre_key", cprefix = "session_pre_key_", cheader_filename = "signal/session_pre_key.h,signal_helper.h")] public class PreKeyRecord : TypeBase { public static int create(out PreKeyRecord pre_key, uint32 id, ECKeyPair key_pair); //public static int deserialize(out PreKeyRecord pre_key, uint8[] data, NativeContext global_context); [CCode (instance_pos = 2)] public int serialze(out Buffer buffer); public uint32 id { get; } public ECKeyPair key_pair { get; } } [Compact] [CCode (cname = "session_record", cprefix = "session_record_", cheader_filename = "signal/signal_protocol_types.h")] public class SessionRecord : TypeBase { public SessionState state { get; } public Buffer user_record { get; } } [Compact] [CCode (cname = "session_state", cprefix = "session_state_", cheader_filename = "signal/session_state.h")] public class SessionState : TypeBase { //public static int create(out SessionState state, NativeContext context); //public static int deserialize(out SessionState state, uint8[] data, NativeContext context); //public static int copy(out SessionState state, SessionState other_state, NativeContext context); [CCode (instance_pos = 2)] public int serialze(out Buffer buffer); public uint32 session_version { get; set; } public ECPublicKey local_identity_key { get; set; } public ECPublicKey remote_identity_key { get; set; } //public Ratchet.RootKey root_key { get; set; } public uint32 previous_counter { get; set; } public ECPublicKey sender_ratchet_key { get; } public ECKeyPair sender_ratchet_key_pair { get; } //public Ratchet.ChainKey sender_chain_key { get; set; } public uint32 remote_registration_id { get; set; } public uint32 local_registration_id { get; set; } public int needs_refresh { get; set; } public ECPublicKey alice_base_key { get; set; } } [Compact] [CCode (cname = "session_signed_pre_key", cprefix = "session_signed_pre_key_", cheader_filename = "signal/session_pre_key.h")] public class SignedPreKeyRecord : TypeBase { public static int create(out SignedPreKeyRecord pre_key, uint32 id, uint64 timestamp, ECKeyPair key_pair, uint8[] signature); [CCode (instance_pos = 2)] public int serialze(out Buffer buffer); public uint32 id { get; } public uint64 timestamp { get; } public ECKeyPair key_pair { get; } public uint8[] signature { [CCode (cname = "session_signed_pre_key_get_signature_")] get { int x = (int)get_signature_len(); unowned uint8[] res = get_signature(); res.length = x; return res; } } [CCode (array_length = false, cname = "session_signed_pre_key_get_signature")] private unowned uint8[] get_signature(); private size_t get_signature_len(); } /** * Address of an Signal Protocol message recipient */ [Compact] [CCode (cname = "signal_protocol_address", cprefix = "signal_protocol_address_", cheader_filename = "signal/signal_protocol.h,signal_helper.h")] public class Address { public Address(string name, int32 device_id); public int32 device_id { get; set; } public string name { owned get; set; } } /** * A representation of a (group + sender + device) tuple */ [Compact] [CCode (cname = "signal_protocol_sender_key_name")] public class SenderKeyName { [CCode (cname = "group_id", array_length_cname="group_id_len")] private char* group_id_; private size_t group_id_len; public Address sender; } [Compact] [CCode (cname = "ec_public_key", cprefix = "ec_public_key_", cheader_filename = "signal/curve.h,signal_helper.h")] public class ECPublicKey : TypeBase { [CCode (cname = "curve_generate_public_key")] public static int generate(out ECPublicKey public_key, ECPrivateKey private_key); [CCode (instance_pos = 1, cname = "ec_public_key_serialize")] private int serialize_([CCode (pos = 0)] out Buffer buffer); [CCode (cname = "ec_public_key_serialize_")] public uint8[] serialize() { Buffer buffer; int code = serialize_(out buffer); if (code < 0 && code > MIN_ERROR_CODE) { // Can only throw for invalid arguments or out of memory. GLib.assert_not_reached(); } return buffer.data; } public int compare(ECPublicKey other); public int memcmp(ECPublicKey other); } [Compact] [CCode (cname = "ec_private_key", cprefix = "ec_private_key_", cheader_filename = "signal/curve.h,signal_helper.h")] public class ECPrivateKey : TypeBase { [CCode (instance_pos = 1, cname = "ec_private_key_serialize")] private int serialize_([CCode (pos = 0)] out Buffer buffer); [CCode (cname = "ec_private_key_serialize_")] public uint8[] serialize() throws GLib.Error { Buffer buffer; int code = serialize_(out buffer); if (code < 0 && code > MIN_ERROR_CODE) { // Can only throw for invalid arguments or out of memory. GLib.assert_not_reached(); } return buffer.data; } public int compare(ECPublicKey other); } [Compact] [CCode (cname = "ec_key_pair", cprefix="ec_key_pair_", cheader_filename = "signal/curve.h,signal_helper.h")] public class ECKeyPair : TypeBase { public static int create(out ECKeyPair key_pair, ECPublicKey public_key, ECPrivateKey private_key); public ECPublicKey public { get; } public ECPrivateKey private { get; } } [CCode (cname = "ratchet_message_keys", cheader_filename = "signal/ratchet.h")] public class MessageKeys { } [Compact] [CCode (cname = "ratchet_identity_key_pair", cprefix = "ratchet_identity_key_pair_", cheader_filename = "signal/ratchet.h,signal_helper.h")] public class IdentityKeyPair : TypeBase { public static int create(out IdentityKeyPair key_pair, ECPublicKey public_key, ECPrivateKey private_key); public int serialze(out Buffer buffer); public ECPublicKey public { get; } public ECPrivateKey private { get; } } [Compact] [CCode (cname = "ec_public_key_list")] public class PublicKeyList {} /** * The main entry point for Signal Protocol encrypt/decrypt operations. * * Once a session has been established with session_builder, * this class can be used for all encrypt/decrypt operations within * that session. */ [Compact] [CCode (cname = "session_cipher", cprefix = "session_cipher_", cheader_filename = "signal/session_cipher.h", free_function = "session_cipher_free")] public class SessionCipher { public void* user_data { get; set; } public DecryptionCallback decryption_callback { set; } [CCode (cname = "session_cipher_encrypt")] private int encrypt_(uint8[] padded_message, out CiphertextMessage encrypted_message); [CCode (cname = "session_cipher_encrypt_")] public CiphertextMessage encrypt(uint8[] padded_message) throws GLib.Error { CiphertextMessage res; throw_by_code(encrypt_(padded_message, out res)); return res; } [CCode (cname = "session_cipher_decrypt_pre_key_signal_message")] private int decrypt_pre_key_signal_message_(PreKeySignalMessage ciphertext, void* decrypt_context, out Buffer plaintext); [CCode (cname = "session_cipher_decrypt_pre_key_signal_message_")] public uint8[] decrypt_pre_key_signal_message(PreKeySignalMessage ciphertext, void* decrypt_context = null) throws GLib.Error { Buffer res; throw_by_code(decrypt_pre_key_signal_message_(ciphertext, decrypt_context, out res)); return res.data; } [CCode (cname = "session_cipher_decrypt_signal_message")] private int decrypt_signal_message_(SignalMessage ciphertext, void* decrypt_context, out Buffer plaintext); [CCode (cname = "session_cipher_decrypt_signal_message_")] public uint8[] decrypt_signal_message(SignalMessage ciphertext, void* decrypt_context = null) throws GLib.Error { Buffer res; throw_by_code(decrypt_signal_message_(ciphertext, decrypt_context, out res)); return res.data; } public int get_remote_registration_id(out uint32 remote_id); public int get_session_version(uint32 version); [CCode (has_target = false)] public delegate int DecryptionCallback(SessionCipher cipher, Buffer plaintext, void* decrypt_context); } [CCode (cname = "int", cheader_filename = "signal/protocol.h", has_type_id = false)] public enum CiphertextType { [CCode (cname = "CIPHERTEXT_SIGNAL_TYPE")] SIGNAL, [CCode (cname = "CIPHERTEXT_PREKEY_TYPE")] PREKEY, [CCode (cname = "CIPHERTEXT_SENDERKEY_TYPE")] SENDERKEY, [CCode (cname = "CIPHERTEXT_SENDERKEY_DISTRIBUTION_TYPE")] SENDERKEY_DISTRIBUTION } [Compact] [CCode (cname = "ciphertext_message", cprefix = "ciphertext_message_", cheader_filename = "signal/protocol.h,signal_helper.h")] public abstract class CiphertextMessage : TypeBase { public CiphertextType type { get; } [CCode (cname = "ciphertext_message_get_serialized")] private unowned Buffer get_serialized_(); public uint8[] serialized { [CCode (cname = "ciphertext_message_get_serialized_")] get { return get_serialized_().data; }} } [Compact] [CCode (cname = "signal_message", cprefix = "signal_message_", cheader_filename = "signal/protocol.h,signal_helper.h")] public class SignalMessage : CiphertextMessage { public ECPublicKey sender_ratchet_key { get; } public uint8 message_version { get; } public uint32 counter { get; } public Buffer body { get; } //public int verify_mac(uint8 message_version, ECPublicKey sender_identity_key, ECPublicKey receiver_identity_key, uint8[] mac, NativeContext global_context); public static int is_legacy(uint8[] data); } [Compact] [CCode (cname = "pre_key_signal_message", cprefix = "pre_key_signal_message_", cheader_filename = "signal/protocol.h,signal_helper.h")] public class PreKeySignalMessage : CiphertextMessage { public uint8 message_version { get; } public ECPublicKey identity_key { get; } public uint32 registration_id { get; } public uint32 pre_key_id { get; } public uint32 signed_pre_key_id { get; } public ECPublicKey base_key { get; } public SignalMessage signal_message { get; } } [Compact] [CCode (cname = "sender_key_message", cprefix = "sender_key_message_", cheader_filename = "signal/protocol.h,signal_helper.h")] public class SenderKeyMessage : CiphertextMessage { public uint32 key_id { get; } public uint32 iteration { get; } public Buffer ciphertext { get; } } [Compact] [CCode (cname = "sender_key_distribution_message", cprefix = "sender_key_distribution_message_", cheader_filename = "signal/protocol.h,signal_helper.h")] public class SenderKeyDistributionMessage : CiphertextMessage { public uint32 id { get; } public uint32 iteration { get; } public Buffer chain_key { get; } public ECPublicKey signature_key { get; } } [CCode (cname = "signal_vala_encrypt", cheader_filename = "signal_helper.h")] private static int aes_encrypt_(out Buffer output, int cipher, uint8[] key, uint8[] iv, uint8[] plaintext, void *user_data); [CCode (cname = "signal_vala_encrypt_")] public uint8[] aes_encrypt(int cipher, uint8[] key, uint8[] iv, uint8[] plaintext) throws GLib.Error { Buffer buf; throw_by_code(aes_encrypt_(out buf, cipher, key, iv, plaintext, null)); return buf.data; } [CCode (cname = "signal_vala_decrypt", cheader_filename = "signal_helper.h")] private static int aes_decrypt_(out Buffer output, int cipher, uint8[] key, uint8[] iv, uint8[] ciphertext, void *user_data); [CCode (cname = "signal_vala_decrypt_")] public uint8[] aes_decrypt(int cipher, uint8[] key, uint8[] iv, uint8[] ciphertext) throws GLib.Error { Buffer buf; throw_by_code(aes_decrypt_(out buf, cipher, key, iv, ciphertext, null)); return buf.data; } } dino-0.1.0/qlite/0000755000000000000000000000000013614354364012266 5ustar rootrootdino-0.1.0/qlite/CMakeLists.txt0000644000000000000000000000205713614354364015032 0ustar rootrootfind_packages(QLITE_PACKAGES REQUIRED Gee GLib GObject SQLite3 ) vala_precompile(QLITE_VALA_C SOURCES "src/database.vala" "src/table.vala" "src/column.vala" "src/row.vala" "src/statement_builder.vala" "src/delete_builder.vala" "src/insert_builder.vala" "src/query_builder.vala" "src/update_builder.vala" "src/upsert_builder.vala" PACKAGES ${QLITE_PACKAGES} GENERATE_VAPI qlite GENERATE_HEADER qlite ) add_custom_target(qlite-vapi DEPENDS ${CMAKE_BINARY_DIR}/exports/qlite.vapi ${CMAKE_BINARY_DIR}/exports/qlite.deps ) add_definitions(${VALA_CFLAGS}) add_library(qlite SHARED ${QLITE_VALA_C}) add_dependencies(qlite qlite-vapi) target_link_libraries(qlite ${QLITE_PACKAGES}) set_target_properties(qlite PROPERTIES VERSION 0.1 SOVERSION 0) install(TARGETS qlite ${TARGET_INSTALL}) install(FILES ${CMAKE_BINARY_DIR}/exports/qlite.vapi ${CMAKE_BINARY_DIR}/exports/qlite.deps DESTINATION ${VAPI_INSTALL_DIR}) install(FILES ${CMAKE_BINARY_DIR}/exports/qlite.h DESTINATION ${INCLUDE_INSTALL_DIR}) dino-0.1.0/qlite/src/0000755000000000000000000000000013614354364013055 5ustar rootrootdino-0.1.0/qlite/src/column.vala0000644000000000000000000001375013614354364015225 0ustar rootrootusing Sqlite; namespace Qlite { public abstract class Column { public const string DEFALT_TABLE_NAME = ""; public string name { get; private set; } public string? default { get; set; } public int sqlite_type { get; private set; } public bool primary_key { get; set; } public bool auto_increment { get; set; } public bool unique { get; set; } public virtual bool not_null { get; set; } public long min_version { get; set; default = -1; } public long max_version { get; set; default = long.MAX; } internal Table table { get; set; } public abstract T get(Row row, string? table_name = DEFALT_TABLE_NAME); public virtual bool is_null(Row row, string? table_name = DEFALT_TABLE_NAME) { return false; } internal abstract void bind(Statement stmt, int index, T value); public string to_string() { return table == null ? name : (table.name + "." + name); } public string to_column_definition() { string res = name; switch (sqlite_type) { case INTEGER: res += " INTEGER"; break; case FLOAT: res += " REAL"; break; case TEXT: res += " TEXT"; break; default: res += " UNKNOWN"; break; } if (primary_key) { res += " PRIMARY KEY"; if (auto_increment) res += " AUTOINCREMENT"; } if (not_null) res += " NOT NULL"; if (unique) res += " UNIQUE"; if (default != null) res += @" DEFAULT $((!) default)"; return res; } Column(string name, int type) { this.name = name; this.sqlite_type = type; } public class Integer : Column { public Integer(string name) { base(name, INTEGER); } public override int get(Row row, string? table_name = DEFALT_TABLE_NAME) { return (int) row.get_integer(name, table_name == DEFALT_TABLE_NAME ? table.name : table_name); } public override bool is_null(Row row, string? table_name = DEFALT_TABLE_NAME) { return !row.has_integer(name, table_name == DEFALT_TABLE_NAME ? table.name : table_name); } internal override void bind(Statement stmt, int index, int value) { stmt.bind_int(index, value); } } public class Long : Column { public Long(string name) { base(name, INTEGER); } public override long get(Row row, string? table_name = DEFALT_TABLE_NAME) { return (long) row.get_integer(name, table_name == DEFALT_TABLE_NAME ? table.name : table_name); } public override bool is_null(Row row, string? table_name = DEFALT_TABLE_NAME) { return !row.has_integer(name, table_name == DEFALT_TABLE_NAME ? table.name : table_name); } internal override void bind(Statement stmt, int index, long value) { stmt.bind_int64(index, value); } } public class Real : Column { public Real(string name) { base(name, FLOAT); } public override double get(Row row, string? table_name = DEFALT_TABLE_NAME) { return row.get_real(name, table_name == DEFALT_TABLE_NAME ? table.name : table_name); } public override bool is_null(Row row, string? table_name = DEFALT_TABLE_NAME) { return !row.has_real(name, table_name == DEFALT_TABLE_NAME ? table.name : table_name); } internal override void bind(Statement stmt, int index, double value) { stmt.bind_double(index, value); } } public class Text : Column { public Text(string name) { base(name, TEXT); } public override string? get(Row row, string? table_name = DEFALT_TABLE_NAME) { return row.get_text(name, table_name == DEFALT_TABLE_NAME ? table.name : table_name); } public override bool is_null(Row row, string? table_name = DEFALT_TABLE_NAME) { return get(row, table_name == DEFALT_TABLE_NAME ? table.name : table_name) == null; } internal override void bind(Statement stmt, int index, string? value) { if (value != null) { stmt.bind_text(index, (!) value); } else { stmt.bind_null(index); } } } public class NonNullText : Column { public NonNullText(string name) { base(name, TEXT); } public override bool not_null { get { return true; } set {} } public override string get(Row row, string? table_name = DEFALT_TABLE_NAME) { return (!)row.get_text(name, table_name == DEFALT_TABLE_NAME ? table.name : table_name); } public override bool is_null(Row row, string? table_name = DEFALT_TABLE_NAME) { return false; } internal override void bind(Statement stmt, int index, string value) { stmt.bind_text(index, (!) value); } } public class BoolText : Column { public BoolText(string name) { base(name, TEXT); } public override bool get(Row row, string? table_name = DEFALT_TABLE_NAME) { return row.get_text(name, table_name == DEFALT_TABLE_NAME ? table.name : table_name) == "1"; } internal override void bind(Statement stmt, int index, bool value) { stmt.bind_text(index, value ? "1" : "0"); } } public class BoolInt : Column { public BoolInt(string name) { base(name, INTEGER); } public override bool get(Row row, string? table_name = DEFALT_TABLE_NAME) { return row.get_integer(name, table_name == DEFALT_TABLE_NAME ? table.name : table_name) == 1; } internal override void bind(Statement stmt, int index, bool value) { stmt.bind_int(index, value ? 1 : 0); } } } } dino-0.1.0/qlite/src/database.vala0000644000000000000000000001203513614354364015467 0ustar rootrootusing Sqlite; namespace Qlite { public class Database { private string file_name; private Sqlite.Database db; private long expected_version; private Table[]? tables; private Column meta_name = new Column.Text("name") { primary_key = true }; private Column meta_int_val = new Column.Long("int_val"); private Column meta_text_val = new Column.Text("text_val"); private Table meta_table; public bool debug = false; public Database(string file_name, long expected_version) { this.file_name = file_name; this.expected_version = expected_version; meta_table = new Table(this, "_meta"); meta_table.init({meta_name, meta_int_val, meta_text_val}); } public void init(Table[] tables) { Sqlite.config(Config.SERIALIZED); int ec = Sqlite.Database.open_v2(file_name, out db, OPEN_READWRITE | OPEN_CREATE | 0x00010000); if (ec != Sqlite.OK) { error(@"SQLite error: %d - %s", db.errcode(), db.errmsg()); } this.tables = tables; start_migration(); if (debug) db.trace((message) => print(@"Qlite trace: $message\n")); } public void ensure_init() { if (tables == null) error(@"Database $file_name was not initialized, call init()"); } private void start_migration() { try { exec("BEGIN TRANSACTION"); } catch (Error e) { error("SQLite error: %d - %s", db.errcode(), db.errmsg()); } meta_table.create_table_at_version(expected_version); long old_version = 0; old_version = meta_table.row_with(meta_name, "version")[meta_int_val, -1]; if (old_version == -1) { foreach (Table t in tables) { t.create_table_at_version(expected_version); } meta_table.insert().value(meta_name, "version").value(meta_int_val, expected_version).perform(); } else if (expected_version != old_version) { foreach (Table t in tables) { t.create_table_at_version(old_version); } foreach (Table t in tables) { t.add_columns_for_version(old_version, expected_version); } migrate(old_version); foreach (Table t in tables) { t.delete_columns_for_version(old_version, expected_version); } if (old_version == -1) { meta_table.insert().value(meta_name, "version").value(meta_int_val, expected_version).perform(); } else { meta_table.update().with(meta_name, "=", "version").set(meta_int_val, expected_version).perform(); } } foreach (Table t in tables) { t.post(); } try { exec("END TRANSACTION"); } catch (Error e) { error("SQLite error: %d - %s", db.errcode(), db.errmsg()); } } internal int errcode() { return db.errcode(); } internal string errmsg() { return db.errmsg(); } internal int64 last_insert_rowid() { return db.last_insert_rowid(); } // To be implemented by actual implementation if required // new table columns are added, outdated columns are still present and will be removed afterwards public virtual void migrate(long old_version) { } public QueryBuilder select(Column[]? columns = null) { ensure_init(); return new QueryBuilder(this).select(columns); } internal MatchQueryBuilder match_query(Table table) { ensure_init(); return new MatchQueryBuilder(this, table); } public InsertBuilder insert() { ensure_init(); return new InsertBuilder(this); } public UpdateBuilder update(Table table) { ensure_init(); return new UpdateBuilder(this, table); } public UpsertBuilder upsert(Table table) { ensure_init(); return new UpsertBuilder(this, table); } public UpdateBuilder update_named(string table) { ensure_init(); return new UpdateBuilder.for_name(this, table); } public DeleteBuilder delete() { ensure_init(); return new DeleteBuilder(this); } public RowIterator query_sql(string sql, string[]? args = null) { ensure_init(); return new RowIterator(this, sql, args); } internal Statement prepare(string sql) { ensure_init(); Sqlite.Statement statement; if (db.prepare_v2(sql, sql.length, out statement) != OK) { error("SQLite error: %d - %s", db.errcode(), db.errmsg()); } return statement; } public void exec(string sql) throws Error { ensure_init(); if (db.exec(sql) != OK) { throw new Error(-1, 0, "SQLite error: %d - %s", db.errcode(), db.errmsg()); } } public bool is_known_column(string table, string field) { ensure_init(); foreach (Table t in tables) { if (t.is_known_column(field)) return true; } return false; } } } dino-0.1.0/qlite/src/delete_builder.vala0000644000000000000000000000332013614354364016670 0ustar rootrootusing Sqlite; namespace Qlite { public class DeleteBuilder : StatementBuilder { // DELETE FROM [...] private Table? table; private string table_name; // WHERE [...] private string selection = "1"; private StatementBuilder.AbstractField[] selection_args = {}; internal DeleteBuilder(Database db) { base(db); } public DeleteBuilder from(Table table) { if (this.table != null) error("Qlite Error: ILLEGAL QUERY: cannot use from() multiple times."); this.table = table; this.table_name = table.name; return this; } public DeleteBuilder from_name(string table) { this.table_name = table; return this; } public DeleteBuilder where(string selection, string[]? selection_args = null) { if (this.selection != "1") error("selection was already done, but where() was called."); this.selection = selection; foreach (string arg in selection_args) { this.selection_args += new StatementBuilder.StringField(arg); } return this; } public DeleteBuilder with(Column column, string comp, T value) { selection_args += new Field(column, value); selection = @"($selection) AND $(column.name) $comp ?"; return this; } internal override Statement prepare() { Statement stmt = db.prepare(@"DELETE FROM $table_name WHERE $selection"); for (int i = 0; i < selection_args.length; i++) { selection_args[i].bind(stmt, i+1); } return stmt; } public void perform() { if (prepare().step() != DONE) { critical(@"SQLite error: %d - %s", db.errcode(), db.errmsg()); } } } } dino-0.1.0/qlite/src/insert_builder.vala0000644000000000000000000000415213614354364016736 0ustar rootrootusing Sqlite; namespace Qlite { public class InsertBuilder : StatementBuilder { // INSERT [OR ...] private bool replace_val; private string? or_val; // INTO [...] private Table table; private string table_name; // VALUES [...] private StatementBuilder.AbstractField[] fields = {}; internal InsertBuilder(Database db) { base(db); } public InsertBuilder replace() { this.replace_val = true; return this; } public InsertBuilder or(string or) { this.or_val = or; return this; } public InsertBuilder into(Table table) { this.table = table; this.table_name = table.name; return this; } public InsertBuilder into_name(string table) { this.table_name = table; return this; } public InsertBuilder value(Column column, T value) { fields += new Field(column, value); return this; } public InsertBuilder value_null(Column column) { if (column.not_null) error("Qlite Error: ILLEGAL QUERY: Can't set non-null column %s to null", column.name); fields += new NullField(column); return this; } internal override Statement prepare() { string fields_text = ""; string value_qs = ""; for (int i = 0; i < fields.length; i++) { if (i != 0) { value_qs += ", "; fields_text += ", "; } fields_text += ((!)fields[i].column).name; value_qs += "?"; } string sql = replace_val ? "REPLACE" : "INSERT"; if (!replace_val && or_val != null) sql += @" OR $((!)or_val)"; sql += @" INTO $table_name ( $fields_text ) VALUES ($value_qs)"; Statement stmt = db.prepare(sql); for (int i = 0; i < fields.length; i++) { fields[i].bind(stmt, i+1); } return stmt; } public int64 perform() { if (prepare().step() != DONE) { critical(@"SQLite error: %d - %s", db.errcode(), db.errmsg()); } return db.last_insert_rowid(); } } } dino-0.1.0/qlite/src/query_builder.vala0000644000000000000000000001632713614354364016606 0ustar rootrootusing Sqlite; namespace Qlite { public class QueryBuilder : StatementBuilder { private bool single_result; // SELECT [...] private string column_selector = "*"; private Column[] columns = {}; // FROM [...] protected Table? table; protected string? table_name; // JOIN [...] private string joins = ""; // WHERE [...] protected string selection = "1"; internal StatementBuilder.AbstractField[] selection_args = {}; // ORDER BY [...] private OrderingTerm[]? order_by_terms = {}; // GROUP BY [...] private string? group_by_term; // LIMIT [...] OFFSET [...] private int limit_val; private int offset_val; internal QueryBuilder(Database db) { base(db); } public QueryBuilder select(Column[] columns = {}) { this.columns = columns; if (columns.length != 0) { for (int i = 0; i < columns.length; i++) { if (column_selector == "*") { column_selector = columns[i].to_string(); } else { column_selector += ", " + columns[i].to_string(); } } } else { column_selector = "*"; } return this; } public QueryBuilder select_string(string column_selector) { this.columns = {}; this.column_selector = column_selector; return this; } public virtual QueryBuilder from(Table table) { if (this.table_name != null) error("cannot use from() multiple times."); this.table = table; this.table_name = table.name; return this; } public virtual QueryBuilder from_name(string table) { this.table_name = table; return this; } public QueryBuilder outer_join_with(Table table, Column lhs, Column rhs, string? as = null) { return outer_join_on(table, @"$lhs = $rhs", as); } public QueryBuilder outer_join_on(Table table, string on, string? as = null) { if (as == null) as = table.name; joins += @" LEFT OUTER JOIN $(table.name) AS $as ON $on"; return this; } public QueryBuilder join_with(Table table, Column lhs, Column rhs, string? as = null) { return join_on(table, @"$lhs = $rhs", as); } public QueryBuilder join_on(Table table, string on, string? as = null) { if (as == null) as = table.name; joins += @" JOIN $(table.name) AS $as ON $on"; return this; } internal QueryBuilder join_name(string table_name, string on) { joins += @" JOIN $table_name ON $on"; return this; } public QueryBuilder where(string selection, string[] selection_args = {}) { this.selection = @"($(this.selection)) AND ($selection)"; foreach (string arg in selection_args) { this.selection_args += new StatementBuilder.StringField(arg); } return this; } public QueryBuilder with(Column column, string comp, T value) { if ((column.unique || column.primary_key) && comp == "=") single_result = true; selection_args += new Field(column, value); selection = @"($selection) AND $column $comp ?"; return this; } public QueryBuilder with_null(Column column) { selection = @"($selection) AND $column ISNULL"; return this; } public QueryBuilder without_null(Column column) { selection = @"($selection) AND $column NOT NULL"; return this; } public QueryBuilder order_by(Column column, string dir = "ASC") { order_by_terms += new OrderingTerm(column, dir); return this; } public QueryBuilder order_by_name(string name, string dir) { order_by_terms += new OrderingTerm.by_name(name, dir); return this; } public QueryBuilder group_by(Column[] columns) { foreach(Column col in columns) { if (group_by_term == null) { group_by_term = col.to_string(); } else { group_by_term += @", $col"; } } return this; } public QueryBuilder limit(int limit) { if (this.limit_val != 0 && limit > this.limit_val) error("tried to increase an existing limit"); this.limit_val = limit; return this; } public QueryBuilder offset(int offset) { if (this.limit_val == 0) error("limit required before offset"); this.offset_val = offset; return this; } public QueryBuilder single() { this.single_result = true; return limit(1); } public int64 count() { this.column_selector = @"COUNT($column_selector) AS count"; this.single_result = true; return row().get_integer("count"); } private Row? row_() { if (!single_result) error("query is not suited to return a single row, but row() was called."); return iterator().get_next(); } public RowOption row() { return new RowOption(row_()); } public T get(Column field, T def = null) { return row().get(field, def); } internal override Statement prepare() { Statement stmt = db.prepare(@"SELECT $column_selector $(table_name == null ? "" : @"FROM $((!) table_name)") $joins WHERE $selection $(group_by_term == null ? "" : @"GROUP BY $group_by_term") $(OrderingTerm.all_to_string(order_by_terms)) $(limit_val > 0 ? @" LIMIT $limit_val OFFSET $offset_val" : "")"); for (int i = 0; i < selection_args.length; i++) { selection_args[i].bind(stmt, i+1); } return stmt; } public RowIterator iterator() { return new RowIterator.from_query_builder(db, this); } class OrderingTerm { Column? column; string column_name; string dir; public OrderingTerm(Column column, string dir) { this.column = column; this.column_name = column.to_string(); this.dir = dir; } public OrderingTerm.by_name(string column_name, string dir) { this.column_name = column_name; this.dir = dir; } public string to_string() { return @"$column_name $dir"; } public static string all_to_string(OrderingTerm[]? terms) { if (terms == null || terms.length == 0) return ""; string res = "ORDER BY "+terms[0].to_string(); for (int i = 1; i < terms.length; i++) { res += @", $(terms[i])"; } return res; } } } public class MatchQueryBuilder : QueryBuilder { internal MatchQueryBuilder(Database db, Table table) { base(db); if (table.fts_columns == null) error("MATCH query on non FTS table"); from(table); join_name(@"_fts_$table_name", @"_fts_$table_name.docid = $table_name.rowid"); } public MatchQueryBuilder match(Column column, string match) { if (table == null) error("MATCH must occur after FROM statement"); if (!(column in table.fts_columns)) error("MATCH selection on non FTS column"); selection_args += new StatementBuilder.StringField(match); selection = @"($selection) AND _fts_$table_name.$(column.name) MATCH ?"; return this; } } } dino-0.1.0/qlite/src/row.vala0000644000000000000000000001007013614354364014527 0ustar rootrootusing Gee; using Sqlite; namespace Qlite { public class Row { private Map text_map = new HashMap(); private Map int_map = new HashMap(); private Map real_map = new HashMap(); internal Row(Statement stmt) { for (int i = 0; i < stmt.column_count(); i++) { string column_name; if (stmt.column_origin_name(i) != null) { column_name = @"$(stmt.column_table_name(i)).$(stmt.column_origin_name(i))"; } else { column_name = stmt.column_name(i); } switch(stmt.column_type(i)) { case TEXT: text_map[column_name] = stmt.column_text(i); break; case INTEGER: int_map[column_name] = (long) stmt.column_int64(i); break; case FLOAT: real_map[column_name] = stmt.column_double(i); break; } } } public T get(Column field) { return field[this]; } private string field_name(string field, string? table) { if (table != null) { return @"$table.$field"; } else { return field; } } public string? get_text(string field, string? table = null) { if (text_map.has_key(field_name(field, table))) { return text_map[field_name(field, table)]; } return null; } public long get_integer(string field, string? table = null) { return int_map[field_name(field, table)]; } public bool has_integer(string field, string? table = null) { return int_map.has_key(field_name(field, table)); } public double get_real(string field, string? table = null, double def = 0) { return real_map[field_name(field, table)] ?? def; } public bool has_real(string field, string? table = null) { return real_map.has_key(field_name(field, table)) && real_map[field_name(field, table)] != null; } public string to_string() { string ret = "{"; foreach (string key in text_map.keys) { if (ret.length > 1) ret += ", "; ret = @"$ret$key: \"$(text_map[key])\""; } foreach (string key in int_map.keys) { if (ret.length > 1) ret += ", "; ret = @"$ret$key: $(int_map[key])"; } foreach (string key in real_map.keys) { if (ret.length > 1) ret += ", "; ret = @"$ret$key: $(real_map[key])"; } return ret + "}"; } } public class RowIterator { private Database db; private Statement stmt; public RowIterator.from_query_builder(Database db, QueryBuilder query) { this.db = db; this.stmt = query.prepare(); } public RowIterator(Database db, string sql, string[]? args = null) { this.db = db; this.stmt = db.prepare(sql); if (args != null) { for (int i = 0; i < args.length; i++) { stmt.bind_text(i, sql, sql.length); } } } public bool next() { int r = stmt.step(); if (r == Sqlite.ROW) return true; if (r == Sqlite.DONE) return false; warning(@"SQLite error: $(db.errcode()) - $(db.errmsg())"); return false; } public Row get() { return new Row(stmt); } public Row? get_next() { if (next()) return get(); return null; } } public class RowOption { public Row? inner { get; private set; } public RowOption(Row? row) { this.inner = row; } public bool is_present() { return inner != null; } public T get(Column field, T def = null) { if (inner == null || field.is_null((!)inner)) return def; return field[(!)inner]; } internal long get_integer(string field, long def = 0) { if (inner == null || !((!)inner).has_integer(field)) return def; return ((!)inner).get_integer(field); } } } dino-0.1.0/qlite/src/statement_builder.vala0000644000000000000000000000245313614354364017440 0ustar rootrootusing Sqlite; namespace Qlite { public abstract class StatementBuilder { protected Database db; internal StatementBuilder(Database db) { this.db = db; } internal abstract Statement prepare(); internal abstract class AbstractField { public T value; public Column? column; AbstractField(T value) { this.value = value; } internal abstract void bind(Statement stmt, int index); } internal class Field : AbstractField { public Field(Column column, T value) { base(value); this.column = column; } internal override void bind(Statement stmt, int index) { ((!)column).bind(stmt, index, value); } } internal class NullField : AbstractField { public NullField(Column column) { base(null); this.column = column; } internal override void bind(Statement stmt, int index) { stmt.bind_null(index); } } internal class StringField : AbstractField { public StringField(string value) { base(value); } internal override void bind(Statement stmt, int index) { stmt.bind_text(index, value); } } } } dino-0.1.0/qlite/src/table.vala0000644000000000000000000001646413614354364015024 0ustar rootrootusing Sqlite; namespace Qlite { public class Table { protected Database db; public string name { get; private set; } protected Column[]? columns; private string constraints = ""; private string[] post_statements = {}; private string[] create_statements = {}; internal Column[]? fts_columns; public Table(Database db, string name) { this.db = db; this.name = name; } public void init(Column[] columns, string constraints = "") { this.columns = columns; this.constraints = constraints; foreach(Column c in columns) { c.table = this; } } public void fts(Column[] columns) { if (fts_columns != null) error("Only one FTS index may be used per table."); fts_columns = columns; string cs = ""; string cnames = ""; string cnews = ""; foreach (Column c in columns) { cs += @", $(c.to_column_definition())"; cnames += @", $(c.name)"; cnews += @", new.$(c.name)"; } add_create_statement(@"CREATE VIRTUAL TABLE IF NOT EXISTS _fts_$name USING fts4(tokenize=unicode61, content=\"$name\"$cs)"); add_post_statement(@"CREATE TRIGGER IF NOT EXISTS _fts_bu_$(name) BEFORE UPDATE ON $name BEGIN DELETE FROM _fts_$name WHERE docid=old.rowid; END"); add_post_statement(@"CREATE TRIGGER IF NOT EXISTS _fts_bd_$(name) BEFORE DELETE ON $name BEGIN DELETE FROM _fts_$name WHERE docid=old.rowid; END"); add_post_statement(@"CREATE TRIGGER IF NOT EXISTS _fts_au_$(name) AFTER UPDATE ON $name BEGIN INSERT INTO _fts_$name(docid$cnames) VALUES(new.rowid$cnews); END"); add_post_statement(@"CREATE TRIGGER IF NOT EXISTS _fts_ai_$(name) AFTER INSERT ON $name BEGIN INSERT INTO _fts_$name(docid$cnames) VALUES(new.rowid$cnews); END"); } public void fts_rebuild() { if (fts_columns == null) error("FTS not available on this table."); try { db.exec(@"INSERT INTO _fts_$name(_fts_$name) VALUES('rebuild');"); } catch (Error e) { critical(@"Qlite Error: Rebuilding FTS index: $(e.message)"); } } public void unique(Column[] columns, string? on_conflict = null) { constraints += ", UNIQUE ("; bool first = true; foreach (Column c in columns) { if (!first) constraints += ", "; constraints += c.name; first = false; } constraints += ")"; if (on_conflict != null) { constraints += " ON CONFLICT " + (!)on_conflict; } } public void add_post_statement(string stmt) { post_statements += stmt; } public void add_create_statement(string stmt) { create_statements += stmt; } public void index(string index_name, Column[] columns, bool unique = false) { string stmt = @"CREATE $(unique ? "UNIQUE" : "") INDEX IF NOT EXISTS $index_name ON $name ("; bool first = true; foreach (Column c in columns) { if (!first) stmt += ", "; stmt += c.name; first = false; } stmt += ")"; add_post_statement(stmt); } private void ensure_init() { if (columns == null) error("Table %s was not initialized, call init()", name); } public QueryBuilder select(Column[]? columns = null) { ensure_init(); return db.select(columns).from(this); } private MatchQueryBuilder match_query() { ensure_init(); return db.match_query(this); } public MatchQueryBuilder match(Column column, string query) { return match_query().match(column, query); } public InsertBuilder insert() { ensure_init(); return db.insert().into(this); } public UpdateBuilder update() { ensure_init(); return db.update(this); } public UpsertBuilder upsert() { ensure_init(); return db.upsert(this); } public DeleteBuilder delete() { ensure_init(); return db.delete().from(this); } public RowOption row_with(Column column, T value) { ensure_init(); if (!column.unique && !column.primary_key) error("%s is not suited to identify a row, but used with row_with()", column.name); return select().with(column, "=", value).row(); } public bool is_known_column(string column) { ensure_init(); foreach (Column c in columns) { if (c.name == column) return true; } return false; } public void create_table_at_version(long version) { ensure_init(); string sql = @"CREATE TABLE IF NOT EXISTS $name ("; bool first = true; for (int i = 0; i < columns.length; i++) { Column c = columns[i]; if (c.min_version <= version && c.max_version >= version) { sql += @"$(!first ? "," : "") $(c.to_column_definition())"; first = false; } } sql += @"$constraints)"; try { db.exec(sql); } catch (Error e) { error(@"Qlite Error: Create table at version: $(e.message)"); } foreach (string stmt in create_statements) { try { db.exec(stmt); } catch (Error e) { error(@"Qlite Error: Create table at version: $(e.message)"); } } } public void add_columns_for_version(long old_version, long new_version) { ensure_init(); foreach (Column c in columns) { if (c.min_version <= new_version && c.max_version >= new_version && c.min_version > old_version) { try { db.exec(@"ALTER TABLE $name ADD COLUMN $(c.to_column_definition())"); } catch (Error e) { critical(@"Qlite Error: Add columns for version: $(e.message)"); } } } } public void delete_columns_for_version(long old_version, long new_version) { bool column_deletion_required = false; string column_list = ""; foreach (Column c in columns) { if (c.min_version <= new_version && c.max_version >= new_version) { if (column_list == "") { column_list = c.name; } else { column_list += ", " + c.name; } } if (!(c.min_version <= new_version && c.max_version >= new_version) && c.min_version <= old_version && c.max_version >= old_version) { column_deletion_required = true; } } if (column_deletion_required) { try { db.exec(@"ALTER TABLE $name RENAME TO _$(name)_$old_version"); create_table_at_version(new_version); db.exec(@"INSERT INTO $name ($column_list) SELECT $column_list FROM _$(name)_$old_version"); db.exec(@"DROP TABLE _$(name)_$old_version"); } catch (Error e) { error(@"Qlite Error: Delete columns for version change: $(e.message)"); } } } internal void post() { foreach (string stmt in post_statements) { try { db.exec(stmt); } catch (Error e) { error(@"Qlite Error: Post: $(e.message)"); } } } } } dino-0.1.0/qlite/src/update_builder.vala0000644000000000000000000000554713614354364016725 0ustar rootrootusing Sqlite; namespace Qlite { public class UpdateBuilder : StatementBuilder { // UPDATE [OR ...] private string? or_val; // [...] private Table? table; private string table_name; // SET [...] private StatementBuilder.AbstractField[] fields = {}; // WHERE [...] private string selection = "1"; private StatementBuilder.AbstractField[] selection_args = {}; internal UpdateBuilder(Database db, Table table) { base(db); this.table = table; this.table_name = table.name; } internal UpdateBuilder.for_name(Database db, string table) { base(db); this.table_name = table; } public UpdateBuilder or(string or) { this.or_val = or; return this; } public UpdateBuilder set(Column column, T value) { fields += new Field(column, value); return this; } public UpdateBuilder set_null(Column column) { if (column.not_null) error("Can't set non-null column %s to null", column.name); fields += new NullField(column); return this; } public UpdateBuilder where(string selection, string[] selection_args = {}) { if (this.selection != "1") error("selection was already done, but where() was called."); this.selection = selection; foreach (string arg in selection_args) { this.selection_args += new StatementBuilder.StringField(arg); } return this; } public UpdateBuilder with(Column column, string comp, T value) { selection_args += new Field(column, value); selection = @"($selection) AND $(column.name) $comp ?"; return this; } public UpdateBuilder with_null(Column column) { selection = @"($selection) AND $(column.name) ISNULL"; return this; } public UpdateBuilder without_null(Column column) { selection = @"($selection) AND $(column.name) NOT NULL"; return this; } internal override Statement prepare() { string sql = "UPDATE"; if (or_val != null) sql += @" OR $((!)or_val)"; sql += @" $table_name SET "; for (int i = 0; i < fields.length; i++) { if (i != 0) { sql += ", "; } sql += @"$(((!)fields[i].column).name) = ?"; } sql += @" WHERE $selection"; Statement stmt = db.prepare(sql); for (int i = 0; i < fields.length; i++) { fields[i].bind(stmt, i+1); } for (int i = 0; i < selection_args.length; i++) { selection_args[i].bind(stmt, i + fields.length + 1); } return stmt; } public void perform() { if (fields.length == 0) return; if (prepare().step() != DONE) { critical("SQLite error: %d - %s", db.errcode(), db.errmsg()); } } } } dino-0.1.0/qlite/src/upsert_builder.vala0000644000000000000000000000631113614354364016753 0ustar rootrootusing Sqlite; namespace Qlite { public class UpsertBuilder : StatementBuilder { // INTO [...] private Table table; private string table_name; // VALUES [...] private StatementBuilder.AbstractField[] keys = {}; private StatementBuilder.AbstractField[] fields = {}; internal UpsertBuilder(Database db, Table table) { base(db); this.table = table; this.table_name = table.name; } public UpsertBuilder value(Column column, T value, bool key = false) { if (key) { keys += new Field(column, value); } else { fields += new Field(column, value); } return this; } public UpsertBuilder value_null(Column column) { if (column.not_null) error("Can't set non-null column %s to null", column.name); fields += new NullField(column); return this; } internal override Statement prepare() { error("prepare() not available for upsert."); } internal Statement prepare_update() { string update_set_list = ""; string update_where_list = ""; for (int i = 0; i < fields.length; i++) { if (i != 0) { update_set_list += ", "; } update_set_list += @"$(((!)fields[i].column).name) = ?"; } for (int i = 0; i < keys.length; i++) { if (i != 0) { update_where_list += " AND "; } update_where_list += @"$(((!)keys[i].column).name) = ?"; } string sql = @"UPDATE $table_name SET $update_set_list WHERE $update_where_list"; Statement stmt = db.prepare(sql); for (int i = 0; i < fields.length; i++) { fields[i].bind(stmt, i + 1); } for (int i = 0; i < keys.length; i++) { keys[i].bind(stmt, i + fields.length + 1); } return stmt; } internal Statement prepare_insert() { string insert_field_list = ""; string insert_value_qs = ""; for (int i = 0; i < fields.length; i++) { if (i != 0) { insert_value_qs += ", "; insert_field_list += ", "; } insert_field_list += ((!)fields[i].column).name; insert_value_qs += "?"; } for (int i = 0; i < keys.length; i++) { if (i != 0 || fields.length > 0) { insert_value_qs += ", "; insert_field_list += ", "; } insert_field_list += ((!)keys[i].column).name; insert_value_qs += "?"; } string sql = @"INSERT OR IGNORE INTO $table_name ($insert_field_list) VALUES ($insert_value_qs)"; Statement stmt = db.prepare(sql); for (int i = 0; i < fields.length; i++) { fields[i].bind(stmt, i + 1); } for (int i = 0; i < keys.length; i++) { keys[i].bind(stmt, i + fields.length + 1); } return stmt; } public int64 perform() { if (prepare_update().step() != DONE || prepare_insert().step() != DONE) { critical(@"SQLite error: %d - %s", db.errcode(), db.errmsg()); } return db.last_insert_rowid(); } } } dino-0.1.0/xmpp-vala/0000755000000000000000000000000013614354364013055 5ustar rootrootdino-0.1.0/xmpp-vala/CMakeLists.txt0000644000000000000000000001171613614354364015623 0ustar rootrootfind_packages(ENGINE_PACKAGES REQUIRED GDKPixbuf2 Gee GIO GLib GObject ICU ) set(ENGINE_DEFINITIONS "") find_package(GIO) if(GIO_VERSION VERSION_GREATER "2.60") message(STATUS "ALPN support enabled") set(ENGINE_DEFINITIONS ALPN_SUPPORT) else() message(STATUS "No ALPN support, needs GIO >= 2.60") endif() set(ENGINE_EXTRA_OPTIONS ${MAIN_EXTRA_OPTIONS} --vapidir=${CMAKE_CURRENT_SOURCE_DIR}/vapi) vala_precompile(ENGINE_VALA_C SOURCES "src/core/namespace_state.vala" "src/core/stanza_attribute.vala" "src/core/stanza_node.vala" "src/core/stanza_reader.vala" "src/core/stanza_writer.vala" "src/core/xmpp_log.vala" "src/core/xmpp_stream.vala" "src/module/bind.vala" "src/module/bookmarks_provider.vala" "src/module/conference.vala" "src/module/iq/module.vala" "src/module/iq/stanza.vala" "src/module/jid.vala" "src/module/message/module.vala" "src/module/message/stanza.vala" "src/module/presence/flag.vala" "src/module/presence/module.vala" "src/module/presence/stanza.vala" "src/module/roster/flag.vala" "src/module/roster/item.vala" "src/module/roster/module.vala" "src/module/roster/versioning_module.vala" "src/module/sasl.vala" "src/module/session.vala" "src/module/stanza.vala" "src/module/stanza_error.vala" "src/module/stream_error.vala" "src/module/tls.vala" "src/module/util.vala" "src/module/xep/0048_bookmarks.vala" "src/module/xep/0048_conference.vala" "src/module/xep/0402_bookmarks2.vala" "src/module/xep/0004_data_forms.vala" "src/module/xep/0030_service_discovery/flag.vala" "src/module/xep/0030_service_discovery/identity.vala" "src/module/xep/0030_service_discovery/info_result.vala" "src/module/xep/0030_service_discovery/item.vala" "src/module/xep/0030_service_discovery/items_result.vala" "src/module/xep/0030_service_discovery/module.vala" "src/module/xep/0045_muc/flag.vala" "src/module/xep/0045_muc/module.vala" "src/module/xep/0045_muc/status_code.vala" "src/module/xep/0047_in_band_bytestreams.vala" "src/module/xep/0049_private_xml_storage.vala" "src/module/xep/0054_vcard/module.vala" "src/module/xep/0060_pubsub.vala" "src/module/xep/0065_socks5_bytestreams.vala" "src/module/xep/0066_out_of_band_data.vala" "src/module/xep/0077_in_band_registration.vala" "src/module/xep/0082_date_time_profiles.vala" "src/module/xep/0084_user_avatars.vala" "src/module/xep/0085_chat_state_notifications.vala" "src/module/xep/0115_entitiy_capabilities.vala" "src/module/xep/0166_jingle.vala" "src/module/xep/0184_message_delivery_receipts.vala" "src/module/xep/0191_blocking_command.vala" "src/module/xep/0198_stream_management.vala" "src/module/xep/0199_ping.vala" "src/module/xep/0203_delayed_delivery.vala" "src/module/xep/0234_jingle_file_transfer.vala" "src/module/xep/0260_jingle_socks5_bytestreams.vala" "src/module/xep/0261_jingle_in_band_bytestreams.vala" "src/module/xep/0280_message_carbons.vala" "src/module/xep/0313_message_archive_management.vala" "src/module/xep/0333_chat_markers.vala" "src/module/xep/0334_message_processing_hints.vala" "src/module/xep/0359_unique_stable_stanza_ids.vala" "src/module/xep/0363_http_file_upload.vala" "src/module/xep/0368_srv_records_tls.vala" "src/module/xep/0380_explicit_encryption.vala" "src/module/xep/0391_jingle_encrypted_transports.vala" "src/module/xep/pixbuf_storage.vala" "src/util.vala" PACKAGES ${ENGINE_PACKAGES} GENERATE_VAPI xmpp-vala GENERATE_HEADER xmpp-vala CUSTOM_VAPIS "${CMAKE_CURRENT_SOURCE_DIR}/src/glib_fixes.vapi" DEFINITIONS ${ENGINE_DEFINITIONS} OPTIONS ${ENGINE_EXTRA_OPTIONS} ) add_custom_target(xmpp-vala-vapi DEPENDS ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi ${CMAKE_BINARY_DIR}/exports/xmpp-vala.deps ) add_definitions(${VALA_CFLAGS} -DG_LOG_DOMAIN="xmpp-vala") add_library(xmpp-vala SHARED ${ENGINE_VALA_C}) add_dependencies(xmpp-vala xmpp-vala-vapi) target_link_libraries(xmpp-vala ${ENGINE_PACKAGES}) set_target_properties(xmpp-vala PROPERTIES VERSION 0.1 SOVERSION 0) install(TARGETS xmpp-vala ${TARGET_INSTALL}) install(FILES ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi ${CMAKE_BINARY_DIR}/exports/xmpp-vala.deps DESTINATION ${VAPI_INSTALL_DIR}) install(FILES ${CMAKE_BINARY_DIR}/exports/xmpp-vala.h DESTINATION ${INCLUDE_INSTALL_DIR}) if(BUILD_TESTS) vala_precompile(ENGINE_TEST_VALA_C SOURCES "tests/common.vala" "tests/testcase.vala" "tests/jid.vala" "tests/stanza.vala" "tests/util.vala" CUSTOM_VAPIS ${CMAKE_BINARY_DIR}/exports/xmpp-vala_internal.vapi PACKAGES ${ENGINE_PACKAGES} OPTIONS ${ENGINE_EXTRA_OPTIONS} ) add_definitions(${VALA_CFLAGS}) add_executable(xmpp-vala-test ${ENGINE_TEST_VALA_C}) target_link_libraries(xmpp-vala-test xmpp-vala ${SIGNAL_PROTOCOL_PACKAGES}) endif(BUILD_TESTS) dino-0.1.0/xmpp-vala/src/0000755000000000000000000000000013614354364013644 5ustar rootrootdino-0.1.0/xmpp-vala/src/core/0000755000000000000000000000000013614354364014574 5ustar rootrootdino-0.1.0/xmpp-vala/src/core/namespace_state.vala0000644000000000000000000000477113614354364020606 0ustar rootrootusing Gee; namespace Xmpp { public class NamespaceState { private HashMap uri_to_name = new HashMap (); private HashMap name_to_uri = new HashMap (); public string current_ns_uri; private NamespaceState parent; public NamespaceState() { add_assoc(XMLNS_URI, "xmlns"); add_assoc(XML_URI, "xml"); current_ns_uri = XML_URI; } public NamespaceState.for_stanza() { this(); add_assoc("http://etherx.jabber.org/streams", "stream"); current_ns_uri = "jabber:client"; } private NamespaceState.copy(NamespaceState old) { foreach (string key in old.uri_to_name.keys) { add_assoc(key, old.uri_to_name[key]); } set_current(old.current_ns_uri); } private NamespaceState.with_parent(NamespaceState parent) { this.copy(parent); this.parent = parent; } public NamespaceState.with_assoc(NamespaceState old, string ns_uri, string name) { this.copy(old); add_assoc(ns_uri, name); } public NamespaceState.with_current(NamespaceState old, string current_ns_uri) { this.copy(old); set_current(current_ns_uri); } public void add_assoc(string ns_uri, string name) { name_to_uri[name] = ns_uri; uri_to_name[ns_uri] = name; } public void set_current(string current_ns_uri) { this.current_ns_uri = current_ns_uri; } public string find_name(string ns_uri) throws XmlError { if (uri_to_name.has_key(ns_uri)) { return uri_to_name[ns_uri]; } throw new XmlError.NS_DICT_ERROR(@"NS URI $ns_uri not found."); } public string find_uri(string name) throws XmlError { if (name_to_uri.has_key(name)) { return name_to_uri[name]; } throw new XmlError.NS_DICT_ERROR(@"NS name $name not found."); } public NamespaceState push() { return new NamespaceState.with_parent(this); } public NamespaceState pop() { return parent; } public string to_string() { StringBuilder sb = new StringBuilder (); sb.append ("NamespaceState{"); foreach (string key in uri_to_name.keys) { sb.append(key); sb.append_c('='); sb.append(uri_to_name[key]); sb.append_c(','); } sb.append("current="); sb.append(current_ns_uri); sb.append_c('}'); return sb.str; } } } dino-0.1.0/xmpp-vala/src/core/stanza_attribute.vala0000644000000000000000000000534113614354364021027 0ustar rootrootnamespace Xmpp { public class StanzaAttribute : StanzaEntry { internal const string ATTRIBUTE_STRING_FORMAT = "{%s}:%s='%s'"; internal const string ATTRIBUTE_STRING_NO_NS_FORMAT = "%s='%s'"; internal const string ATTRIBUTE_STRING_ANSI_FORMAT = ANSI_COLOR_GRAY+"{%s}:"+ANSI_COLOR_END+"%s="+ANSI_COLOR_GREEN+"'%s'"+ANSI_COLOR_END; internal const string ATTRIBUTE_STRING_ANSI_NO_NS_FORMAT = "%s="+ANSI_COLOR_GREEN+"'%s'"+ANSI_COLOR_END; internal const string ATTRIBUTE_XML_FORMAT = "%s:%s='%s'"; internal const string ATTRIBUTE_XML_NO_NS_FORMAT = "%s='%s'"; internal const string ATTRIBUTE_XML_ANSI_FORMAT = "%s:%s="+ANSI_COLOR_GREEN+"'%s'"+ANSI_COLOR_END; internal const string ATTRIBUTE_XML_ANSI_NO_NS_FORMAT = "%s="+ANSI_COLOR_GREEN+"'%s'"+ANSI_COLOR_END; internal StanzaAttribute() { } public StanzaAttribute.build(string ns_uri, string name, string val) { this.ns_uri = ns_uri; this.name = name; this.val = val; } public bool equals(StanzaAttribute other) { if (other.ns_uri != ns_uri) return false; if (other.name != name) return false; if (other.val != val) return false; return true; } internal string printf(string fmt, bool no_ns = false, string? ns_name = null) { if (no_ns) { return fmt.printf(name, (!)encoded_val); } else { if (ns_name == null) { return fmt.printf((!)ns_uri, name, (!)encoded_val); } else { return fmt.printf((!)ns_name, name, (!)encoded_val); } } } public override string to_string(int i = 0) { return printf(ATTRIBUTE_STRING_FORMAT); } public string to_ansi_string(bool hide_ns = false) { if (hide_ns) { return printf(ATTRIBUTE_STRING_ANSI_NO_NS_FORMAT, true); } else { return printf(ATTRIBUTE_STRING_ANSI_FORMAT); } } public string to_xml(NamespaceState? state_ = null) throws XmlError { NamespaceState state = state_ ?? new NamespaceState(); if (ns_uri == state.current_ns_uri || (ns_uri == XMLNS_URI && name == "xmlns")) { return printf(ATTRIBUTE_XML_NO_NS_FORMAT, true); } else { return printf(ATTRIBUTE_XML_FORMAT, false, state.find_name((!)ns_uri)); } } public string to_ansi_xml(NamespaceState? state_ = null) throws XmlError { NamespaceState state = state_ ?? new NamespaceState(); if (ns_uri == state.current_ns_uri || (ns_uri == XMLNS_URI && name == "xmlns")) { return printf(ATTRIBUTE_XML_ANSI_NO_NS_FORMAT, true); } else { return printf(ATTRIBUTE_XML_ANSI_FORMAT, false, state.find_name((!)ns_uri)); } } } } dino-0.1.0/xmpp-vala/src/core/stanza_node.vala0000644000000000000000000003642213614354364017755 0ustar rootrootusing Gee; namespace Xmpp { public abstract class StanzaEntry { protected const string ANSI_COLOR_END = "\x1b[0m"; protected const string ANSI_COLOR_GREEN = "\x1b[32m"; protected const string ANSI_COLOR_YELLOW = "\x1b[33m"; protected const string ANSI_COLOR_GRAY = "\x1b[37m"; public string? ns_uri; public string name; public string? val; public string? encoded_val { owned get { if (val == null) return null; return ((!)val).replace("&", "&").replace("\"", """).replace("'", "'").replace("<", "<").replace(">", ">"); } set { if (value == null) { val = null; return; } string tmp = ((!)value).replace(">", ">").replace("<", "<").replace("'","'").replace(""","\""); while (tmp.contains("&#")) { int start = tmp.index_of("&#"); int end = tmp.index_of(";", start); if (end < start) break; unichar num = -1; if (tmp[start+2]=='x') { tmp.substring(start+3, start-end-3).scanf("%x", &num); } else { num = int.parse(tmp.substring(start+2, start-end-2)); } tmp = tmp.splice(start, end, num.to_string()); } val = tmp.replace("&", "&"); } } public virtual unowned string? get_string_content() { return val; } public virtual string to_string(int i = 0) { return get_string_content() ?? "(null)"; } } public class StanzaNode : StanzaEntry { public Gee.List sub_nodes = new ArrayList(); public Gee.List attributes = new ArrayList(); public bool has_nodes = false; public bool pseudo = false; internal StanzaNode() { } public StanzaNode.build(string name, string ns_uri = "jabber:client", ArrayList? nodes = null, ArrayList? attrs = null) { this.ns_uri = ns_uri; this.name = name; if (nodes != null) this.sub_nodes.add_all((!)nodes); if (attrs != null) this.attributes.add_all((!)attrs); } public StanzaNode.text(string text) { this.name = "#text"; this.val = text; } public StanzaNode.encoded_text(string text) { this.name = "#text"; this.encoded_val = text; } public StanzaNode add_self_xmlns() { if (ns_uri == null) return this; return put_attribute("xmlns", (!)ns_uri); } public unowned string? get_attribute(string name, string? ns_uri = null) { string _name = name; string? _ns_uri = ns_uri; if (_ns_uri == null) { if (_name.contains(":")) { var lastIndex = _name.last_index_of_char(':'); _ns_uri = _name.substring(0, lastIndex); _name = _name.substring(lastIndex + 1); } else { _ns_uri = this.ns_uri; } } foreach (var attr in attributes) { if (attr.ns_uri == (!)_ns_uri && attr.name == _name) return attr.val; } return null; } public int get_attribute_int(string name, int def = -1, string? ns_uri = null) { string? res = get_attribute(name, ns_uri); if (res == null) return def; return int.parse((!)res); } public uint get_attribute_uint(string name, uint def = 0, string? ns_uri = null) { string? res = get_attribute(name, ns_uri); if (res == null) return def; return (uint) long.parse((!)res); } public bool get_attribute_bool(string name, bool def = false, string? ns_uri = null) { string? res = get_attribute(name, ns_uri); if (res == null) return def; return ((!)res).down() == "true" || res == "1"; } public StanzaAttribute? get_attribute_raw(string name, string? ns_uri = null) { string _name = name; string? _ns_uri = ns_uri; if (_ns_uri == null) { if (_name.contains(":")) { var lastIndex = _name.last_index_of_char(':'); _ns_uri = _name.substring(0, lastIndex); _name = _name.substring(lastIndex + 1); } else { _ns_uri = this.ns_uri; } } foreach (var attr in attributes) { if (attr.ns_uri == _ns_uri && attr.name == _name) return attr; } return null; } public Gee.List get_attributes_by_ns_uri(string ns_uri) { ArrayList ret = new ArrayList (); foreach (var attr in attributes) { if (attr.ns_uri == ns_uri) ret.add(attr); } return ret; } public unowned string? get_deep_attribute(...) { va_list l = va_list(); StanzaAttribute? res = get_deep_attribute_(va_list.copy(l)); if (res == null) return null; return ((!)res).val; } public StanzaAttribute? get_deep_attribute_(va_list l) { StanzaNode node = this; string? attribute_name = l.arg(); if (attribute_name == null) return null; while (true) { string? s = l.arg(); if (s == null) break; StanzaNode? node_tmp = node.get_subnode((!)attribute_name); if (node_tmp == null) return null; node = (!)node_tmp; attribute_name = s; } return node.get_attribute_raw((!)attribute_name); } public StanzaNode? get_subnode(string name, string? ns_uri = null, bool recurse = false) { string _name = name; string? _ns_uri = ns_uri; if (ns_uri == null) { if (_name.contains(":")) { var lastIndex = _name.last_index_of_char(':'); _ns_uri = _name.substring(0, lastIndex); _name = _name.substring(lastIndex + 1); } else { _ns_uri = this.ns_uri; } } foreach (var node in sub_nodes) { if (node.ns_uri == _ns_uri && node.name == _name) return node; if (recurse) { var x = node.get_subnode(_name, _ns_uri, recurse); if (x != null) return x; } } return null; } public Gee.List get_subnodes(string name, string? ns_uri = null, bool recurse = false) { ArrayList ret = new ArrayList(); string _name = name; string? _ns_uri = ns_uri; if (ns_uri == null) { if (_name.contains(":")) { var lastIndex = _name.last_index_of_char(':'); _ns_uri = _name.substring(0, lastIndex); _name = _name.substring(lastIndex + 1); } else { _ns_uri = this.ns_uri; } } foreach (var node in sub_nodes) { if (node.ns_uri == _ns_uri && node.name == _name) ret.add(node); if (recurse) { ret.add_all(node.get_subnodes(_name, _ns_uri, recurse)); } } return ret; } public StanzaNode? get_deep_subnode(...) { va_list l = va_list(); return get_deep_subnode_(va_list.copy(l)); } public StanzaNode? get_deep_subnode_(va_list l) { StanzaNode node = this; while (true) { string? s = l.arg(); if (s == null) break; StanzaNode? node_tmp = node.get_subnode((!)s); if (node_tmp == null) return null; node = (!)node_tmp; } return node; } public Gee.List get_deep_subnodes(...) { va_list l = va_list(); return get_deep_subnodes_(va_list.copy(l)); } public Gee.List get_deep_subnodes_(va_list l) { StanzaNode node = this; string? subnode_name = l.arg(); if (subnode_name == null) return new ArrayList(); while (true) { string? s = l.arg(); if (s == null) break; StanzaNode? node_tmp = node.get_subnode((!)subnode_name); if (node_tmp == null) return new ArrayList(); node = (!)node_tmp; subnode_name = s; } return node.get_subnodes((!)subnode_name); } public Gee.List get_all_subnodes() { return sub_nodes; } public Gee.List get_deep_all_subnodes(...) { va_list l = va_list(); StanzaNode? node = get_deep_subnode_(va_list.copy(l)); if (node != null) return ((!)node).get_all_subnodes(); return new ArrayList(); } public void add_attribute(StanzaAttribute attr) { attributes.add(attr); } public override unowned string? get_string_content() { if (val != null) return val; if (sub_nodes.size == 1) return sub_nodes[0].get_string_content(); return null; } public unowned string? get_deep_string_content(...) { va_list l = va_list(); StanzaNode? node = get_deep_subnode_(va_list.copy(l)); if (node != null) return ((!)node).get_string_content(); return null; } public StanzaNode put_attribute(string name, string val, string? ns_uri = null) { string? _ns_uri = ns_uri; if (name == "xmlns") _ns_uri = XMLNS_URI; if (_ns_uri == null) _ns_uri = this.ns_uri; if (_ns_uri == null) return this; attributes.add(new StanzaAttribute.build((!)_ns_uri, name, val)); return this; } /** * Set only occurrence **/ public void set_attribute(string name, string val, string? ns_uri = null) { if (ns_uri == null) ns_uri = this.ns_uri; foreach (var attr in attributes) { if (attr.ns_uri == ns_uri && attr.name == name) { attr.val = val; return; } } put_attribute(name, val, ns_uri); } public StanzaNode put_node(StanzaNode node) { sub_nodes.add(node); return this; } public bool equals(StanzaNode other) { if (other.name != name) return false; if (other.val != val) return false; if (name == "#text") return true; if (other.ns_uri != ns_uri) return false; if (other.sub_nodes.size != sub_nodes.size) return false; for (int i = 0; i < sub_nodes.size; i++) { if (!other.sub_nodes[i].equals(sub_nodes[i])) return false; } if (other.attributes.size != attributes.size) return false; for (int i = 0; i < attributes.size; i++) { if (!other.attributes[i].equals(attributes[i])) return false; } return true; } private const string TAG_START_BEGIN_FORMAT = "%s<{%s}:%s"; private const string TAG_START_EMPTY_END = " />\n"; private const string TAG_START_CONTENT_END = ">\n"; private const string TAG_END_FORMAT = "%s\n"; private const string TAG_ANSI_START_BEGIN_FORMAT = "%s"+ANSI_COLOR_YELLOW+"<"+ANSI_COLOR_GRAY+"{%s}:"+ANSI_COLOR_YELLOW+"%s"+ANSI_COLOR_END; private const string TAG_ANSI_START_BEGIN_NO_NS_FORMAT = "%s"+ANSI_COLOR_YELLOW+"<%s"+ANSI_COLOR_END; private const string TAG_ANSI_START_EMPTY_END = ANSI_COLOR_YELLOW+" />"+ANSI_COLOR_END+"\n"; private const string TAG_ANSI_START_CONTENT_END = ANSI_COLOR_YELLOW+">"+ANSI_COLOR_END+"\n"; private const string TAG_ANSI_END_FORMAT = "%s"+ANSI_COLOR_YELLOW+""+ANSI_COLOR_END+"\n"; private const string TAG_ANSI_END_NO_NS_FORMAT = "%s"+ANSI_COLOR_YELLOW+""+ANSI_COLOR_END+"\n"; internal string printf(int i, string fmt_start_begin, string start_empty_end, string start_content_end, string fmt_end, string fmt_attr, bool no_ns = false) { string indent = string.nfill (i * 2, ' '); if (name == "#text") { if (((!)val).length > 1000) { return indent + "[... retracted for brevity ...]\n"; } return indent + ((!)val).replace("\n", indent + "\n") + "\n"; } var sb = new StringBuilder(); if (no_ns) { sb.append_printf(fmt_start_begin, indent, name); } else { sb.append_printf(fmt_start_begin, indent, (!)ns_uri, name); } foreach (StanzaAttribute attr in attributes) { sb.append_printf(" %s", attr.printf(fmt_attr, no_ns)); } if (!has_nodes && sub_nodes.size == 0) { sb.append(start_empty_end); } else { sb.append(start_content_end); if (sub_nodes.size != 0) { foreach (StanzaNode subnode in sub_nodes) { sb.append(subnode.printf(i+1, fmt_start_begin, start_empty_end, start_content_end, fmt_end, fmt_attr, no_ns)); } if (no_ns) { sb.append_printf(fmt_end, indent, name); } else { sb.append_printf(fmt_end, indent, (!)ns_uri, name); } } } return sb.str; } public override string to_string(int i = 0) { return printf(i, TAG_START_BEGIN_FORMAT, TAG_START_EMPTY_END, TAG_START_CONTENT_END, TAG_END_FORMAT, StanzaAttribute.ATTRIBUTE_STRING_FORMAT); } public string to_ansi_string(bool hide_ns = false, int i = 0) { if (hide_ns) { return printf(i, TAG_ANSI_START_BEGIN_NO_NS_FORMAT, TAG_ANSI_START_EMPTY_END, TAG_ANSI_START_CONTENT_END, TAG_ANSI_END_NO_NS_FORMAT, StanzaAttribute.ATTRIBUTE_STRING_ANSI_NO_NS_FORMAT, true); } else { return printf(i, TAG_ANSI_START_BEGIN_FORMAT, TAG_ANSI_START_EMPTY_END, TAG_ANSI_START_CONTENT_END, TAG_ANSI_END_FORMAT, StanzaAttribute.ATTRIBUTE_STRING_ANSI_FORMAT); } } public string to_xml(NamespaceState? state = null) throws XmlError { NamespaceState my_state = state ?? new NamespaceState.for_stanza(); if (name == "#text") return val == null ? "" : (!)encoded_val; my_state = my_state.push(); foreach (var xmlns in get_attributes_by_ns_uri (XMLNS_URI)) { if (xmlns.val == null) continue; if (xmlns.name == "xmlns") { my_state.set_current((!)xmlns.val); } else { my_state.add_assoc((!)xmlns.val, xmlns.name); } } var sb = new StringBuilder(); if (ns_uri == my_state.current_ns_uri) { sb.append_printf("<%s", name); } else { sb.append_printf("<%s:%s", my_state.find_name ((!)ns_uri), name); } var attr_ns_state = new NamespaceState.with_current(my_state, (!)ns_uri); foreach (StanzaAttribute attr in attributes) { sb.append_printf(" %s", attr.to_xml(attr_ns_state)); } if (!has_nodes && sub_nodes.size == 0) { sb.append("/>"); } else { sb.append(">"); if (sub_nodes.size != 0) { foreach (StanzaNode subnode in sub_nodes) { sb.append(subnode.to_xml(my_state)); } if (ns_uri == my_state.current_ns_uri) { sb.append(@""); } else { sb.append_printf("", my_state.find_name ((!)ns_uri), name); } } } my_state = my_state.pop(); return sb.str; } } } dino-0.1.0/xmpp-vala/src/core/stanza_reader.vala0000644000000000000000000002335413614354364020272 0ustar rootrootusing Gee; namespace Xmpp { public const string XMLNS_URI = "http://www.w3.org/2000/xmlns/"; public const string XML_URI = "http://www.w3.org/XML/1998/namespace"; public const string JABBER_URI = "jabber:client"; public errordomain XmlError { NS_DICT_ERROR, UNSUPPORTED, EOF, BAD_XML, IO, TLS } public class StanzaReader { private static int BUFFER_MAX = 4096; private InputStream? input; private uint8[] buffer; private int buffer_fill = 0; private int buffer_pos = 0; private Cancellable cancellable = new Cancellable(); private NamespaceState ns_state = new NamespaceState(); public StanzaReader.for_buffer(uint8[] buffer) { this.buffer = buffer; this.buffer_fill = buffer.length; } public StanzaReader.for_string(string s) { this.for_buffer(s.data); } public StanzaReader.for_stream(InputStream input) { this.input = input; buffer = new uint8[BUFFER_MAX]; } public void cancel() { cancellable.cancel(); } private async void update_buffer() throws XmlError { try { InputStream? input = this.input; if (input == null) throw new XmlError.EOF("No input stream specified and end of buffer reached."); if (cancellable.is_cancelled()) throw new XmlError.EOF("Input stream is canceled."); buffer_fill = (int) yield ((!)input).read_async(buffer, GLib.Priority.DEFAULT, cancellable); if (buffer_fill == 0) throw new XmlError.EOF("End of input stream reached."); buffer_pos = 0; } catch (GLib.IOError e) { throw new XmlError.IO("GLib.IOError: %s".printf(e.message)); } } private async char read_single() throws XmlError { if (buffer_pos >= buffer_fill) { yield update_buffer(); } return (char) buffer[buffer_pos++]; } private async char peek_single() throws XmlError { if (buffer_pos >= buffer_fill) { yield update_buffer(); } return (char) buffer[buffer_pos]; } private bool is_ws(uint8 what) { return what == ' ' || what == '\t' || what == '\r' || what == '\n'; } private void skip_single() { buffer_pos++; } private async void skip_until_non_ws() throws XmlError { if (buffer_pos >= buffer_fill) { yield update_buffer(); } while (is_ws(buffer[buffer_pos])) { buffer_pos++; if (buffer_pos >= buffer_fill) { yield update_buffer(); } } } private async string read_until_ws() throws XmlError { var res = new StringBuilder(); if (buffer_pos >= buffer_fill) { yield update_buffer(); } while (!is_ws(buffer[buffer_pos])) { res.append_c((char) buffer[buffer_pos++]); if (buffer_pos >= buffer_fill) { yield update_buffer(); } } return res.str; } private async string read_until_char_or_ws(char x, char y = 0) throws XmlError { var res = new StringBuilder(); if (buffer_pos >= buffer_fill) { yield update_buffer(); } while (buffer[buffer_pos] != x && buffer[buffer_pos] != y && !is_ws(buffer[buffer_pos])) { res.append_c((char) buffer[buffer_pos++]); if (buffer_pos >= buffer_fill) { yield update_buffer(); } } return res.str; } private async string read_until_char(char x) throws XmlError { var res = new StringBuilder(); if (buffer_pos >= buffer_fill) { yield update_buffer(); } while (buffer[buffer_pos] != x) { res.append_c((char) buffer[buffer_pos++]); if (buffer_pos >= buffer_fill) { yield update_buffer(); } } return res.str; } private async StanzaAttribute read_attribute() throws XmlError { var res = new StanzaAttribute(); res.name = yield read_until_char_or_ws('='); if ((yield read_single()) == '=') { var quot = yield peek_single(); if (quot == '\'' || quot == '"') { skip_single(); res.encoded_val = yield read_until_char(quot); skip_single(); } else { res.encoded_val = yield read_until_ws(); } } return res; } private void handle_entry_ns(StanzaEntry entry, string default_uri = ns_state.current_ns_uri) throws XmlError { if (entry.ns_uri != null) return; if (entry.name.contains(":")) { var split = entry.name.split(":"); entry.ns_uri = ns_state.find_uri(split[0]); entry.name = split[1]; } else { entry.ns_uri = default_uri; } } private void handle_stanza_ns(StanzaNode res) throws XmlError { foreach (StanzaAttribute attr in res.attributes) { if (attr.name == "xmlns" && attr.val != null) { attr.ns_uri = XMLNS_URI; ns_state.set_current((!)attr.val); } else if (attr.name.contains(":") && attr.val != null) { var split = attr.name.split(":"); if (split[0] == "xmlns") { attr.ns_uri = XMLNS_URI; attr.name = split[1]; ns_state.add_assoc((!)attr.val, attr.name); } } } handle_entry_ns(res); foreach (StanzaAttribute attr in res.attributes) { handle_entry_ns(attr, res.ns_uri ?? ns_state.current_ns_uri); } } public async StanzaNode read_node_start() throws XmlError { var res = new StanzaNode(); res.attributes = new ArrayList(); var eof = false; if ((yield peek_single()) == '<') skip_single(); if ((yield peek_single()) == '?') res.pseudo = true; if ((yield peek_single()) == '/') { eof = true; skip_single(); res.name = yield read_until_char_or_ws('>'); while ((yield peek_single()) != '>') { skip_single(); } skip_single(); res.has_nodes = false; res.pseudo = false; handle_stanza_ns(res); return res; } res.name = yield read_until_char_or_ws('>', '/'); yield skip_until_non_ws(); char next_char = yield peek_single(); while (next_char != '/' && next_char != '>' && next_char != '?') { res.attributes.add(yield read_attribute()); yield skip_until_non_ws(); next_char = yield peek_single(); } if ((yield read_single()) == '/' || res.pseudo) { res.has_nodes = false; skip_single(); } else { res.has_nodes = true; } handle_stanza_ns(res); return res; } public async StanzaNode read_text_node() throws XmlError { var res = new StanzaNode(); res.name = "#text"; res.ns_uri = ns_state.current_ns_uri; res.encoded_val = (yield read_until_char('<')).strip(); return res; } public async StanzaNode read_root_node() throws XmlError { yield skip_until_non_ws(); if ((yield peek_single()) == '<') { var res = yield read_node_start(); if (res.pseudo) { return yield read_root_node(); } return res; } else { throw new XmlError.BAD_XML("Content before root node"); } } public async StanzaNode read_stanza_node() throws XmlError { try { ns_state = ns_state.push(); var res = yield read_node_start(); if (res.has_nodes) { bool finish_node_seen = false; do { yield skip_until_non_ws(); if ((yield peek_single()) == '<') { skip_single(); if ((yield peek_single()) == '/') { skip_single(); string desc = yield read_until_char('>'); skip_single(); if (desc.contains(":")) { var split = desc.split(":"); if (split[0] != ns_state.find_name((!)res.ns_uri)) throw new XmlError.BAD_XML(""); if (split[1] != res.name) throw new XmlError.BAD_XML(""); } else { if (ns_state.current_ns_uri != res.ns_uri) throw new XmlError.BAD_XML(""); if (desc != res.name) throw new XmlError.BAD_XML(""); } finish_node_seen = true; } else { res.sub_nodes.add(yield read_stanza_node()); } } else { res.sub_nodes.add(yield read_text_node()); } } while (!finish_node_seen); if (res.sub_nodes.size == 0) res.has_nodes = false; } ns_state = ns_state.pop(); return res; } catch (XmlError e) { uint8[] buffer_cpy = new uint8[buffer.length + 1]; Memory.copy(buffer_cpy, buffer, buffer.length); warning("XmlError at: %s".printf((string)buffer_cpy) + "\n"); throw e; } } public async StanzaNode read_node() throws XmlError { yield skip_until_non_ws(); if ((yield peek_single()) == '<') { return yield read_stanza_node(); } else { return yield read_text_node(); } } } } dino-0.1.0/xmpp-vala/src/core/stanza_writer.vala0000644000000000000000000000241213614354364020334 0ustar rootrootnamespace Xmpp { public class StanzaWriter { private OutputStream output; private Queue queue = new Queue(); private bool running = false; public StanzaWriter.for_stream(OutputStream output) { this.output = output; } public async void write_node(StanzaNode node) throws XmlError { yield write_data(node.to_xml().data); } public async void write(string s) throws XmlError { yield write_data(s.data); } private async void write_data(uint8[] data) throws XmlError { if (running) { queue.push_tail(new SourceFuncWrapper(write_data.callback)); yield; } running = true; try { yield output.write_all_async(data, 0, null, null); } catch (GLib.Error e) { throw new XmlError.IO(@"IOError in GLib: $(e.message)"); } finally { SourceFuncWrapper? sfw = queue.pop_head(); if (sfw != null) { sfw.sfun(); } else { running = false; } } } } public class SourceFuncWrapper : Object { public SourceFunc sfun; public SourceFuncWrapper(owned SourceFunc sfun) { this.sfun = (owned)sfun; } } } dino-0.1.0/xmpp-vala/src/core/xmpp_log.vala0000644000000000000000000001132313614354364017266 0ustar rootrootusing Gee; namespace Xmpp { public class XmppLog { protected const string ANSI_COLOR_END = "\x1b[0m"; protected const string ANSI_COLOR_WHITE = "\x1b[37;1m"; class NodeLogDesc { public string? name; private string? ns_uri; private string? val; private Map attrs = new HashMap(); private NodeLogDesc? inner; public NodeLogDesc(string desc) { string d = desc; if (d.contains("[")) { int start = d.index_of("["); int end = d.index_of("]"); string attrs = d.substring(start + 1, end - start - 1); d = d.substring(0, start) + d.substring(end + 1); foreach (string attr in attrs.split(",")) { if (attr.contains("=")) { string key = attr.substring(0, attr.index_of("=")); string val = attr.substring(attr.index_of("=") + 1); this.attrs[key] = val; } else { this.attrs[attr] = null; } } } if (d.contains(":") && d.index_of("{") == 0 && d.index_of("}") != -1) { int end = d.index_of("}"); this.ns_uri = d.substring(1, end - 1); d = d.substring(end + 2); } if (d.contains(".")) { inner = new NodeLogDesc(d.substring(d.index_of(".") + 1)); d = d.substring(0, d.index_of(".")); } else if (d.contains("=")) { this.val = d.substring(d.index_of("=")); d = d.substring(0, d.index_of("=")); } if (d != "") this.name = d; } public bool matches(StanzaNode node) { if (name != null && node.name != name) return false; if (ns_uri != null && node.ns_uri != ns_uri) return false; if (val != null && node.val != val) return false; foreach (var pair in attrs.entries) { if (pair.value == null && node.get_attribute(pair.key) == null) return false; else if (pair.value != null && pair.value != node.get_attribute(pair.key)) return false; } if (inner == null) return true; foreach (StanzaNode snode in node.get_all_subnodes()) { if (((!)inner).matches(snode)) return true; } return false; } } private bool use_ansi; private bool hide_ns = true; private string ident; private string desc; private Gee.List descs = new ArrayList(); public XmppLog(string? ident = null, string? desc = null) { this.ident = ident ?? ""; this.desc = desc ?? ""; this.use_ansi = is_atty(stderr.fileno()); while (this.desc.contains(";")) { string opt = this.desc.substring(0, this.desc.index_of(";")); this.desc = this.desc.substring(opt.length + 1); switch (opt) { case "ansi": use_ansi = true; break; case "no-ansi": use_ansi = false; break; case "hide-ns": hide_ns = true; break; case "show-ns": hide_ns = false; break; } } if (desc != "") { foreach (string d in this.desc.split("|")) { descs.add(new NodeLogDesc(d)); } } } public virtual bool should_log_node(StanzaNode node) { if (ident == "" || desc == "") return false; if (desc == "all") return true; foreach (var desc in descs) { if (desc.matches(node)) return true; } return false; } public virtual bool should_log_str(string str) { if (ident == "" || desc == "") return false; if (desc == "all") return true; foreach (var desc in descs) { if (desc.name == "#text") return true; } return false; } public void node(string what, StanzaNode node, XmppStream stream) { if (should_log_node(node)) { stderr.printf("%sXMPP %s [%s %p %s]%s\n%s\n", use_ansi ? ANSI_COLOR_WHITE : "", what, ident, stream, new DateTime.now_local().to_string(), use_ansi ? ANSI_COLOR_END : "", use_ansi ? node.to_ansi_string(hide_ns) : node.to_string()); } } public void str(string what, string str, XmppStream stream) { if (should_log_str(str)) { stderr.printf("%sXMPP %s [%s %p %s]%s\n%s\n", use_ansi ? ANSI_COLOR_WHITE : "", what, ident, stream, new DateTime.now_local().to_string(), use_ansi ? ANSI_COLOR_END : "", str); } } [CCode (cname = "isatty")] private static extern bool is_atty(int fd); } } dino-0.1.0/xmpp-vala/src/core/xmpp_stream.vala0000644000000000000000000003400113614354364017776 0ustar rootrootusing Gee; namespace Xmpp { public errordomain IOStreamError { READ, WRITE, CONNECT, DISCONNECT, TLS } public class XmppStream { public const string NS_URI = "http://etherx.jabber.org/streams"; public Jid remote_name; public XmppLog log = new XmppLog(); public StanzaNode? features { get; private set; default = new StanzaNode.build("features", NS_URI); } private IOStream? stream; private StanzaReader? reader; private StanzaWriter? writer; public Gee.List flags { get; private set; default=new ArrayList(); } public Gee.List modules { get; private set; default=new ArrayList(); } private Gee.List connection_providers = new ArrayList(); public bool negotiation_complete { get; set; default=false; } private bool setup_needed = false; private bool non_negotiation_modules_attached = false; private bool disconnected = false; public signal void received_node(XmppStream stream, StanzaNode node); public signal void received_root_node(XmppStream stream, StanzaNode node); public signal void received_features_node(XmppStream stream); public signal void received_message_stanza(XmppStream stream, StanzaNode node); public signal void received_presence_stanza(XmppStream stream, StanzaNode node); public signal void received_iq_stanza(XmppStream stream, StanzaNode node); public signal void received_nonza(XmppStream stream, StanzaNode node); public signal void stream_negotiated(XmppStream stream); public signal void attached_modules(XmppStream stream); public XmppStream() { register_connection_provider(new StartTlsConnectionProvider()); } public async void connect(string? remote_name = null) throws IOStreamError { try { if (remote_name != null) this.remote_name = new Jid(remote_name); } catch (InvalidJidError e) { throw new IOStreamError.CONNECT(@"Invalid remote name \"$remote_name\": $(e.message)"); } attach_negotation_modules(); try { int min_priority = -1; ConnectionProvider? best_provider = null; foreach (ConnectionProvider connection_provider in connection_providers) { int? priority = yield connection_provider.get_priority(this.remote_name); if (priority != null && (priority < min_priority || min_priority == -1)) { min_priority = priority; best_provider = connection_provider; } } IOStream? stream = null; if (best_provider != null) { stream = yield best_provider.connect(this); } if (stream == null) { debug("Connecting to %s, xmpp-client, tcp (fallback)", this.remote_name.to_string()); stream = yield (new SocketClient()).connect_to_host_async(this.remote_name.to_string(), 5222); } if (stream == null) { throw new IOStreamError.CONNECT("client.connect() returned null"); } reset_stream((!)stream); } catch (Error e) { debug("[%p] Could not connect to server: %s", this, e.message); throw new IOStreamError.CONNECT(e.message); } debug("Connected to %s", remote_name); yield loop(); } public async void disconnect() throws IOStreamError, XmlError, IOError { disconnected = true; if (writer == null || reader == null || stream == null) { throw new IOStreamError.DISCONNECT("trying to disconnect, but no stream open"); } log.str("OUT", "", this); yield writer.write(""); reader.cancel(); yield stream.close_async(); } public void reset_stream(IOStream stream) { this.stream = stream; reader = new StanzaReader.for_stream(stream.input_stream); writer = new StanzaWriter.for_stream(stream.output_stream); require_setup(); } public void require_setup() { setup_needed = true; } public bool is_setup_needed() { return setup_needed; } public async StanzaNode read() throws IOStreamError { StanzaReader? reader = this.reader; if (reader == null) throw new IOStreamError.READ("trying to read, but no stream open"); try { StanzaNode node = yield ((!)reader).read_node(); log.node("IN", node, this); return node; } catch (XmlError e) { throw new IOStreamError.READ(e.message); } } [Version (deprecated = true, deprecated_since = "0.1", replacement = "write_async")] public void write(StanzaNode node) { write_async.begin(node, (obj, res) => { write_async.end(res); }); } public async void write_async(StanzaNode node) throws IOStreamError { StanzaWriter? writer = this.writer; if (writer == null) throw new IOStreamError.WRITE("trying to write, but no stream open"); try { log.node("OUT", node, this); yield ((!)writer).write_node(node); } catch (XmlError e) { throw new IOStreamError.WRITE(e.message); } } internal IOStream? get_stream() { return stream; } public void add_flag(XmppStreamFlag flag) { flags.add(flag); } public bool has_flag(FlagIdentity? identity) { return get_flag(identity) != null; } public T? get_flag(FlagIdentity? identity) { if (identity == null) return null; foreach (var flag in flags) { if (((!)identity).matches(flag)) return ((!)identity).cast(flag); } return null; } public void remove_flag(XmppStreamFlag flag) { flags.remove(flag); } public XmppStream add_module(XmppStreamModule module) { foreach (XmppStreamModule m in modules) { if (m.get_ns() == module.get_ns() && m.get_id() == module.get_id()) { warning("[%p] Adding already added module: %s\n", this, module.get_id()); return this; } } modules.add(module); if (negotiation_complete) module.attach(this); return this; } public void detach_modules() { foreach (XmppStreamModule module in modules) { if (!(module is XmppStreamNegotiationModule) && !negotiation_complete) continue; module.detach(this); } } public T? get_module(ModuleIdentity? identity) { if (identity == null) return null; foreach (var module in modules) { if (((!)identity).matches(module)) return ((!)identity).cast(module); } return null; } public void register_connection_provider(ConnectionProvider connection_provider) { connection_providers.add(connection_provider); } public bool is_negotiation_active() { foreach (XmppStreamModule module in modules) { if (module is XmppStreamNegotiationModule) { XmppStreamNegotiationModule negotiation_module = (XmppStreamNegotiationModule) module; if (negotiation_module.negotiation_active(this)) return true; } } return false; } private async void setup() throws IOStreamError { StanzaNode outs = new StanzaNode.build("stream", "http://etherx.jabber.org/streams") .put_attribute("to", remote_name.to_string()) .put_attribute("version", "1.0") .put_attribute("xmlns", "jabber:client") .put_attribute("stream", "http://etherx.jabber.org/streams", XMLNS_URI); outs.has_nodes = true; log.node("OUT ROOT", outs, this); write(outs); received_root_node(this, yield read_root()); } private async void loop() throws IOStreamError { while (true) { if (setup_needed) { yield setup(); setup_needed = false; } StanzaNode node = yield read(); Idle.add(loop.callback); yield; if (disconnected) break; received_node(this, node); if (node.ns_uri == NS_URI && node.name == "features") { features = node; received_features_node(this); } else if (node.ns_uri == NS_URI && node.name == "stream" && node.pseudo) { debug("[%p] Server closed stream", this); try { yield disconnect(); } catch (Error e) {} return; } else if (node.ns_uri == JABBER_URI) { if (node.name == "message") { received_message_stanza(this, node); } else if (node.name == "presence") { received_presence_stanza(this, node); } else if (node.name == "iq") { received_iq_stanza(this, node); } else { received_nonza(this, node); } } else { received_nonza(this, node); } if (!non_negotiation_modules_attached && negotiation_modules_done()) { attach_non_negotation_modules(); non_negotiation_modules_attached = true; if (!negotiation_complete) { stream_negotiated(this); negotiation_complete = true; } } } } private bool negotiation_modules_done() throws IOStreamError { if (setup_needed) return false; if (is_negotiation_active()) return false; foreach (XmppStreamModule module in modules) { if (module is XmppStreamNegotiationModule) { XmppStreamNegotiationModule negotiation_module = (XmppStreamNegotiationModule) module; if (negotiation_module.mandatory_outstanding(this)) { throw new IOStreamError.CONNECT("mandatory-to-negotiate feature not negotiated: " + negotiation_module.get_id()); } } } return true; } private void attach_non_negotation_modules() { foreach (XmppStreamModule module in modules) { if (module as XmppStreamNegotiationModule == null) { module.attach(this); } } attached_modules(this); } private void attach_negotation_modules() { foreach (XmppStreamModule module in modules) { if (module as XmppStreamNegotiationModule != null) { module.attach(this); } } } private async StanzaNode read_root() throws IOStreamError { StanzaReader? reader = this.reader; if (reader == null) throw new IOStreamError.READ("trying to read, but no stream open"); try { StanzaNode node = yield ((!)reader).read_root_node(); log.node("IN ROOT", node, this); return node; } catch (XmlError.TLS e) { throw new IOStreamError.TLS(e.message); } catch (Error e) { throw new IOStreamError.READ(e.message); } } } public class FlagIdentity : Object { public string ns { get; private set; } public string id { get; private set; } public FlagIdentity(string ns, string id) { this.ns = ns; this.id = id; } public T? cast(XmppStreamFlag flag) { return flag.get_type().is_a(typeof(T)) ? (T?) flag : null; } public bool matches(XmppStreamFlag module) { return module.get_ns() == ns && module.get_id() == id; } } public abstract class XmppStreamFlag : Object { public abstract string get_ns(); public abstract string get_id(); } public class ModuleIdentity : Object { public string ns { get; private set; } public string id { get; private set; } public ModuleIdentity(string ns, string id) { this.ns = ns; this.id = id; } public T? cast(XmppStreamModule module) { return module.get_type().is_a(typeof(T)) ? (T?) module : null; } public bool matches(XmppStreamModule module) { return module.get_ns() == ns && module.get_id() == id; } } public abstract class XmppStreamModule : Object { public abstract void attach(XmppStream stream); public abstract void detach(XmppStream stream); public abstract string get_ns(); public abstract string get_id(); } public abstract class XmppStreamNegotiationModule : XmppStreamModule { public abstract bool mandatory_outstanding(XmppStream stream); public abstract bool negotiation_active(XmppStream stream); } public abstract class ConnectionProvider { public async abstract int? get_priority(Jid remote_name); public async abstract IOStream? connect(XmppStream stream); public abstract string get_id(); } public class StartTlsConnectionProvider : ConnectionProvider { private SrvTarget? srv_target; public async override int? get_priority(Jid remote_name) { GLib.List? xmpp_target = null; try { GLibFixes.Resolver resolver = GLibFixes.Resolver.get_default(); xmpp_target = yield resolver.lookup_service_async("xmpp-client", "tcp", remote_name.to_string(), null); } catch (Error e) { return null; } xmpp_target.sort((a, b) => { return a.get_priority() - b.get_priority(); }); srv_target = xmpp_target.nth(0).data; return xmpp_target.nth(0).data.get_priority(); } public async override IOStream? connect(XmppStream stream) { try { SocketClient client = new SocketClient(); debug("Connecting to %s %i (starttls)", srv_target.get_hostname(), srv_target.get_port()); return yield client.connect_to_host_async(srv_target.get_hostname(), srv_target.get_port()); } catch (Error e) { return null; } } public override string get_id() { return "start_tls"; } } } dino-0.1.0/xmpp-vala/src/glib_fixes.vapi0000644000000000000000000000414713614354364016646 0ustar rootroot[CCode (cprefix = "G", gir_namespace = "Gio", gir_version = "2.0", lower_case_cprefix = "g_")] namespace GLibFixes { [CCode (cheader_filename = "gio/gio.h", type_id = "g_resolver_get_type ()")] public class Resolver : GLib.Object { [CCode (has_construct_function = false)] protected Resolver(); [Version (since = "2.22")] public static Resolver get_default(); [Version (since = "2.22")] public virtual string lookup_by_address(GLib.InetAddress address, GLib.Cancellable? cancellable = null) throws GLib.Error ; [Version (since = "2.22")] public virtual async string lookup_by_address_async(GLib.InetAddress address, GLib.Cancellable? cancellable = null) throws GLib.Error ; [Version (since = "2.22")] public virtual GLib.List lookup_by_name(string hostname, GLib.Cancellable? cancellable = null) throws GLib.Error ; [Version (since = "2.22")] public virtual async GLib.List lookup_by_name_async(string hostname, GLib.Cancellable? cancellable = null) throws GLib.Error ; [Version (since = "2.34")] public virtual GLib.List lookup_records(string rrname, GLib.ResolverRecordType record_type, GLib.Cancellable? cancellable = null) throws GLib.Error ; [Version (since = "2.34")] public virtual async GLib.List lookup_records_async(string rrname, GLib.ResolverRecordType record_type, GLib.Cancellable? cancellable = null) throws GLib.Error ; [Version (since = "2.22")] public virtual GLib.List lookup_service(string service, string protocol, string domain, GLib.Cancellable? cancellable = null) throws GLib.Error ; [CCode (finish_vfunc_name = "lookup_service_finish", vfunc_name = "lookup_service_async")] public async GLib.List lookup_service_async (string service, string protocol, string domain, GLib.Cancellable? cancellable = null) throws GLib.Error; [Version (since = "2.22")] public void set_default(); public virtual signal void reload (); } } dino-0.1.0/xmpp-vala/src/module/0000755000000000000000000000000013614354364015131 5ustar rootrootdino-0.1.0/xmpp-vala/src/module/bind.vala0000644000000000000000000000613513614354364016717 0ustar rootrootnamespace Xmpp.Bind { private const string NS_URI = "urn:ietf:params:xml:ns:xmpp-bind"; /** The parties to a stream MUST consider resource binding as mandatory-to-negotiate. (RFC6120 7.3.1) */ public class Module : XmppStreamNegotiationModule { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "bind_module"); public string? requested_resource { get; set; } public signal void bound_to_resource(XmppStream stream, Jid my_jid); public Module(string? requested_resource) { this.requested_resource = requested_resource; } public void iq_response_stanza(XmppStream stream, Iq.Stanza iq) { var flag = stream.get_flag(Flag.IDENTITY); if (flag == null || flag.finished) return; if (iq.type_ == Iq.Stanza.TYPE_RESULT) { try { flag.my_jid = new Jid(iq.stanza.get_subnode("jid", NS_URI, true).get_string_content()); flag.finished = true; bound_to_resource(stream, flag.my_jid); } catch (InvalidJidError e) { warning("Received invalid Jid when binding: %s", e.message); } } } public void received_features_node(XmppStream stream) { if (stream.is_setup_needed()) return; if (stream.is_negotiation_active()) return; var bind = stream.features.get_subnode("bind", NS_URI); if (bind != null) { var flag = new Flag(); StanzaNode bind_node = new StanzaNode.build("bind", NS_URI).add_self_xmlns(); if (requested_resource != null) { bind_node.put_node(new StanzaNode.build("resource", NS_URI).put_node(new StanzaNode.text(requested_resource))); } stream.get_module(Iq.Module.IDENTITY).send_iq(stream, new Iq.Stanza.set(bind_node), iq_response_stanza); stream.add_flag(flag); } } public override void attach(XmppStream stream) { stream.received_features_node.connect(this.received_features_node); } public override void detach(XmppStream stream) { stream.received_features_node.disconnect(this.received_features_node); } public override bool mandatory_outstanding(XmppStream stream) { return !stream.has_flag(Flag.IDENTITY) || !stream.get_flag(Flag.IDENTITY).finished; } public override bool negotiation_active(XmppStream stream) { return stream.has_flag(Flag.IDENTITY) && !stream.get_flag(Flag.IDENTITY).finished; } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } public class Flag : XmppStreamFlag { public static FlagIdentity IDENTITY = new FlagIdentity(NS_URI, "bind"); public Jid? my_jid; public bool finished = false; public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } } dino-0.1.0/xmpp-vala/src/module/bookmarks_provider.vala0000644000000000000000000000145413614354364021704 0ustar rootrootusing Gee; namespace Xmpp { public interface BookmarksProvider : Object { public signal void conference_added(XmppStream stream, Conference conferences); public signal void conference_removed(XmppStream stream, Jid jid); public signal void conference_changed(XmppStream stream, Conference conferences); public signal void received_conferences(XmppStream stream, Set conferences); public async abstract async Set? get_conferences(XmppStream stream); public async abstract void add_conference(XmppStream stream, Conference conference); public async abstract void remove_conference(XmppStream stream, Conference conference); public async abstract void replace_conference(XmppStream stream, Conference orig_conference, Conference modified_conference); } } dino-0.1.0/xmpp-vala/src/module/conference.vala0000644000000000000000000000076313614354364020113 0ustar rootrootnamespace Xmpp { public class Conference : Object { public virtual Jid? jid { get; set; } public virtual bool autojoin { get; set; } public virtual string? nick { get; set; } public virtual string? name { get; set; } public virtual string? password { get; set; } public static bool equal_func(Conference a, Conference b) { return Jid.equals_func(a.jid, b.jid); } public static uint hash_func(Conference a) { return Jid.hash_func(a.jid); } } } dino-0.1.0/xmpp-vala/src/module/iq/0000755000000000000000000000000013614354364015542 5ustar rootrootdino-0.1.0/xmpp-vala/src/module/iq/module.vala0000644000000000000000000001126513614354364017701 0ustar rootrootusing Gee; namespace Xmpp.Iq { private const string NS_URI = "jabber:client"; public class Module : XmppStreamNegotiationModule { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "iq_module"); private HashMap responseListeners = new HashMap(); private HashMap> namespaceRegistrants = new HashMap>(); public async Iq.Stanza send_iq_async(XmppStream stream, Iq.Stanza iq) { Iq.Stanza? return_stanza = null; send_iq(stream, iq, (_, result_iq) => { return_stanza = result_iq; Idle.add(send_iq_async.callback); }); yield; return return_stanza; } public delegate void OnResult(XmppStream stream, Iq.Stanza iq); public void send_iq(XmppStream stream, Iq.Stanza iq, owned OnResult? listener = null) { stream.write(iq.stanza); if (listener != null) { responseListeners[iq.id] = new ResponseListener((owned) listener); } } public void register_for_namespace(string namespace, Handler module) { if (!namespaceRegistrants.has_key(namespace)) { namespaceRegistrants.set(namespace, new ArrayList()); } namespaceRegistrants[namespace].add(module); } public void unregister_from_namespace(string namespace, Handler module) { ArrayList? handlers = namespaceRegistrants[namespace]; if (handlers != null) handlers.remove(module); } public override void attach(XmppStream stream) { stream.received_iq_stanza.connect(on_received_iq_stanza); } public override void detach(XmppStream stream) { stream.received_iq_stanza.disconnect(on_received_iq_stanza); } public override bool mandatory_outstanding(XmppStream stream) { return false; } public override bool negotiation_active(XmppStream stream) { return false; } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } private void on_received_iq_stanza(XmppStream stream, StanzaNode node) { Iq.Stanza iq = new Iq.Stanza.from_stanza(node, stream.has_flag(Bind.Flag.IDENTITY) ? stream.get_flag(Bind.Flag.IDENTITY).my_jid : null); if (iq.type_ == Iq.Stanza.TYPE_RESULT || iq.is_error()) { if (responseListeners.has_key(iq.id)) { ResponseListener? listener = responseListeners.get(iq.id); if (listener != null) { listener.on_result(stream, iq); } responseListeners.unset(iq.id); } } else { Gee.List children = node.get_all_subnodes(); if (children.size == 1 && namespaceRegistrants.has_key(children[0].ns_uri)) { Gee.List handlers = namespaceRegistrants[children[0].ns_uri]; foreach (Handler handler in handlers) { if (iq.type_ == Iq.Stanza.TYPE_GET) { handler.on_iq_get(stream, iq); } else if (iq.type_ == Iq.Stanza.TYPE_SET) { handler.on_iq_set(stream, iq); } } } else { // Send error if we don't handle the NS of the IQ get/set payload (RFC6120 10.3.3 (2)) Iq.Stanza unavailable_error = new Iq.Stanza.error(iq, new ErrorStanza.service_unavailable()) { to=iq.from }; send_iq(stream, unavailable_error); } } } private class ResponseListener { public OnResult on_result { get; private owned set; } public ResponseListener(owned OnResult on_result) { this.on_result = (owned) on_result; } } } public interface Handler : Object { public virtual void on_iq_get(XmppStream stream, Iq.Stanza iq) { Iq.Stanza bad_request = new Iq.Stanza.error(iq, new ErrorStanza.bad_request("unexpected IQ get for this namespace")); stream.get_module(Module.IDENTITY).send_iq(stream, bad_request); } public virtual void on_iq_set(XmppStream stream, Iq.Stanza iq) { Iq.Stanza bad_request = new Iq.Stanza.error(iq, new ErrorStanza.bad_request("unexpected IQ set for this namespace")); stream.get_module(Module.IDENTITY).send_iq(stream, bad_request); } } } dino-0.1.0/xmpp-vala/src/module/iq/stanza.vala0000644000000000000000000000231613614354364017711 0ustar rootrootusing Gee; namespace Xmpp.Iq { public class Stanza : Xmpp.Stanza { public const string TYPE_GET = "get"; public const string TYPE_RESULT = "result"; public const string TYPE_SET = "set"; private Stanza(string? id = null) { base.outgoing(new StanzaNode.build("iq")); this.id = id ?? random_uuid(); } public Stanza.get(StanzaNode stanza_node, string? id = null) { this(id); this.type_ = TYPE_GET; stanza.put_node(stanza_node); } public Stanza.result(Stanza request, StanzaNode? stanza_node = null) { this(request.id); this.to = request.from; this.type_ = TYPE_RESULT; if (stanza_node != null) { stanza.put_node(stanza_node); } } public Stanza.set(StanzaNode stanza_node, string? id = null) { this(id); this.type_ = TYPE_SET; stanza.put_node(stanza_node); } public Stanza.error(Stanza request, ErrorStanza error_stanza) { this(request.id); this.type_ = TYPE_ERROR; stanza.put_node(error_stanza.error_node); } public Stanza.from_stanza(StanzaNode stanza_node, Jid? my_jid) { base.incoming(stanza_node, my_jid); } } } dino-0.1.0/xmpp-vala/src/module/jid.vala0000644000000000000000000001574413614354364016557 0ustar rootrootnamespace Xmpp { public class Jid { public string? localpart; public string domainpart; public string? resourcepart; public Jid bare_jid { owned get { return is_bare() ? this : new Jid.intern(null, localpart, domainpart, null); } } public Jid domain_jid { owned get { return is_domain() ? this : new Jid.intern(domainpart, null, domainpart, null); } } private string jid; public Jid(string jid) throws InvalidJidError { int slash_index = jid.index_of("/"); int at_index = jid.index_of("@"); if (at_index > slash_index && slash_index != -1) at_index = -1; string resourcepart = slash_index < 0 ? null : jid.slice(slash_index + 1, jid.length); string localpart = at_index < 0 ? null : jid.slice(0, at_index); string domainpart; if (at_index < 0) { if (slash_index < 0) { domainpart = jid; } else { domainpart = jid.slice(0, slash_index); } } else { if (slash_index < 0) { domainpart = jid.slice(at_index + 1, jid.length); } else { domainpart = jid.slice(at_index + 1, slash_index); } } this.components(localpart, domainpart, resourcepart); } private Jid.intern(owned string? jid, owned string? localpart, owned string domainpart, owned string? resourcepart) { this.jid = (owned) jid; this.localpart = (owned) localpart; this.domainpart = (owned) domainpart; this.resourcepart = (owned) resourcepart; } public Jid.components(string? localpart, string domainpart, string? resourcepart) throws InvalidJidError { // TODO verify and normalize all parts if (domainpart.length == 0) throw new InvalidJidError.EMPTY_DOMAIN("Domain is empty"); if (localpart != null && localpart.length == 0) throw new InvalidJidError.EMPTY_LOCAL("Localpart is empty but non-null"); if (resourcepart != null && resourcepart.length == 0) throw new InvalidJidError.EMPTY_RESOURCE("Resource is empty but non-null"); string domain = domainpart[domainpart.length - 1] == '.' ? domainpart.substring(0, domainpart.length - 1) : domainpart; if (domain.contains("xn--")) { domain = idna_decode(domain); } this.localpart = prepare(localpart, ICU.PrepType.RFC3920_NODEPREP); this.domainpart = prepare(domain, ICU.PrepType.RFC3491_NAMEPREP); this.resourcepart = prepare(resourcepart, ICU.PrepType.RFC3920_RESOURCEPREP); idna_verify(this.domainpart); } private static string idna_decode(string src) throws InvalidJidError { ICU.ErrorCode status = ICU.ErrorCode.ZERO_ERROR; ICU.IDNAInfo info; char[] dest = new char[src.length * 2]; ICU.IDNA.openUTS46(ICU.IDNAOptions.DEFAULT, ref status).nameToUnicodeUTF8(src, -1, dest, out info, ref status); if (status == ICU.ErrorCode.INVALID_CHAR_FOUND) { throw new InvalidJidError.INVALID_CHAR("Found invalid character"); } else if (status.is_failure() || info.errors > 0) { throw new InvalidJidError.UNKNOWN(@"Unknown error: $(status.errorName())"); } return (string) dest; } private static void idna_verify(string src) throws InvalidJidError { ICU.ErrorCode status = ICU.ErrorCode.ZERO_ERROR; ICU.IDNAInfo info; char[] dest = new char[src.length * 2]; ICU.IDNA.openUTS46(ICU.IDNAOptions.DEFAULT, ref status).nameToASCII_UTF8(src, -1, dest, out info, ref status); if (status == ICU.ErrorCode.INVALID_CHAR_FOUND) { throw new InvalidJidError.INVALID_CHAR("Found invalid character"); } else if (status.is_failure() || info.errors > 0) { throw new InvalidJidError.UNKNOWN(@"Unknown error: $(status.errorName())"); } } private static string? prepare(string? src, ICU.PrepType type, bool strict = false) throws InvalidJidError { if (src == null) return src; try { ICU.ParseError error; ICU.ErrorCode status = ICU.ErrorCode.ZERO_ERROR; ICU.PrepProfile profile = ICU.PrepProfile.openByType(type, ref status); ICU.String src16 = ICU.String.from_string(src); int32 dest16_capacity = src16.len() * 2 + 1; ICU.String dest16 = ICU.String.alloc(dest16_capacity); long dest16_length = profile.prepare(src16, src16.len(), dest16, dest16_capacity, strict ? ICU.PrepOptions.DEFAULT : ICU.PrepOptions.ALLOW_UNASSIGNED, out error, ref status); if (status == ICU.ErrorCode.INVALID_CHAR_FOUND) { throw new InvalidJidError.INVALID_CHAR("Found invalid character"); } else if (status == ICU.ErrorCode.STRINGPREP_PROHIBITED_ERROR) { throw new InvalidJidError.INVALID_CHAR("Found prohibited character"); } else if (status != ICU.ErrorCode.ZERO_ERROR) { throw new InvalidJidError.UNKNOWN(@"Unknown error: $(status.errorName())"); } else if (dest16_length < 0) { throw new InvalidJidError.UNKNOWN("Unknown error"); } return dest16.to_string(); } catch (ConvertError e) { throw new InvalidJidError.INVALID_CHAR(@"Conversion error: $(e.message)"); } } public Jid with_resource(string? resourcepart) throws InvalidJidError { return new Jid.components(localpart, domainpart, resourcepart); } public bool is_domain() { return localpart == null && resourcepart == null; } public bool is_bare() { return resourcepart == null; } public bool is_full() { return localpart != null && resourcepart != null; } public string to_string() { if (jid == null) { if (localpart != null && resourcepart != null) { jid = @"$localpart@$domainpart/$resourcepart"; } else if (localpart != null) { jid = @"$localpart@$domainpart"; } else if (resourcepart != null) { jid = @"$domainpart/$resourcepart"; } else { jid = domainpart; } } return jid; } public bool equals_bare(Jid? jid) { return jid != null && equals_bare_func(this, jid); } public bool equals(Jid? jid) { return jid != null && equals_func(this, jid); } public static new bool equals_bare_func(Jid jid1, Jid jid2) { return jid1.localpart == jid2.localpart && jid1.domainpart == jid2.domainpart; } public static bool equals_func(Jid jid1, Jid jid2) { return equals_bare_func(jid1, jid2) && jid1.resourcepart == jid2.resourcepart; } public static new uint hash_bare_func(Jid jid) { return jid.bare_jid.to_string().hash(); } public static new uint hash_func(Jid jid) { return jid.to_string().hash(); } } public errordomain InvalidJidError { EMPTY_DOMAIN, EMPTY_RESOURCE, EMPTY_LOCAL, INVALID_CHAR, UNKNOWN } } dino-0.1.0/xmpp-vala/src/module/message/0000755000000000000000000000000013614354364016555 5ustar rootrootdino-0.1.0/xmpp-vala/src/module/message/module.vala0000644000000000000000000000366313614354364020717 0ustar rootrootusing Gee; namespace Xmpp { private const string NS_URI = "jabber:client"; public class MessageModule : XmppStreamModule { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "message_module"); public StanzaListenerHolder received_pipeline = new StanzaListenerHolder(); public StanzaListenerHolder send_pipeline = new StanzaListenerHolder(); public signal void received_message(XmppStream stream, MessageStanza message); public signal void received_message_unprocessed(XmppStream stream, MessageStanza message); public void send_message(XmppStream stream, MessageStanza message) { send_pipeline.run.begin(stream, message, (obj, res) => { stream.write(message.stanza); }); } public async void received_message_stanza_async(XmppStream stream, StanzaNode node) { MessageStanza message = new MessageStanza.from_stanza(node, stream.get_flag(Bind.Flag.IDENTITY).my_jid); received_message_unprocessed(stream, message); if (!message.is_error()) { bool abort = yield received_pipeline.run(stream, message); if (abort) return; received_message(stream, message); } } private void received_message_stanza(XmppStream stream, StanzaNode node) { received_message_stanza_async.begin(stream, node); } public override void attach(XmppStream stream) { stream.received_message_stanza.connect(received_message_stanza); } public override void detach(XmppStream stream) { stream.received_message_stanza.disconnect(received_message_stanza); } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } } dino-0.1.0/xmpp-vala/src/module/message/stanza.vala0000644000000000000000000000363013614354364020724 0ustar rootrootusing Gee; namespace Xmpp { public class MessageStanza : Xmpp.Stanza { public const string NODE_BODY = "body"; public const string NODE_SUBJECT = "subject"; public const string NODE_THREAD = "thread"; public const string TYPE_CHAT = "chat"; public const string TYPE_GROUPCHAT = "groupchat"; public const string TYPE_HEADLINE = "headline"; public const string TYPE_NORMAL = "normal"; public bool rerun_parsing = false; private ArrayList flags = new ArrayList(); public string body { get { StanzaNode? body_node = stanza.get_subnode(NODE_BODY); return body_node == null ? null : body_node.get_string_content(); } set { StanzaNode? body_node = stanza.get_subnode(NODE_BODY); if (body_node == null) { body_node = new StanzaNode.build(NODE_BODY); stanza.put_node(body_node); } body_node.sub_nodes.clear(); body_node.put_node(new StanzaNode.text(value)); } } public override string? type_ { get { return base.type_ ?? TYPE_NORMAL; } set { base.type_ = value; } } public MessageStanza(string? id = null) { base.outgoing(new StanzaNode.build("message")); stanza.set_attribute(ATTRIBUTE_ID, id ?? random_uuid()); } public MessageStanza.from_stanza(StanzaNode stanza_node, Jid my_jid) { base.incoming(stanza_node, my_jid); } public void add_flag(MessageFlag flag) { flags.add(flag); } public MessageFlag? get_flag(string ns, string id) { foreach (MessageFlag flag in flags) { if (flag.get_ns() == ns && flag.get_id() == id) return flag; } return null; } } public abstract class MessageFlag : Object { public abstract string get_ns(); public abstract string get_id(); } } dino-0.1.0/xmpp-vala/src/module/presence/0000755000000000000000000000000013614354364016735 5ustar rootrootdino-0.1.0/xmpp-vala/src/module/presence/flag.vala0000644000000000000000000000336613614354364020523 0ustar rootrootusing Gee; namespace Xmpp.Presence { public class Flag : XmppStreamFlag { public static FlagIdentity IDENTITY = new FlagIdentity(NS_URI, "presence"); private HashMap> resources = new HashMap>(Jid.hash_bare_func, Jid.equals_bare_func); private HashMap presences = new HashMap(Jid.hash_func, Jid.equals_func); public Set get_available_jids() { return resources.keys; } public Gee.List? get_resources(Jid jid) { return resources[jid]; } public Presence.Stanza? get_presence(Jid full_jid) { return presences[full_jid]; } public void add_presence(Presence.Stanza presence) { if (!resources.has_key(presence.from)) { resources[presence.from] = new ArrayList(Jid.equals_func); } if (resources[presence.from].contains(presence.from)) { resources[presence.from].remove(presence.from); } resources[presence.from].add(presence.from); presences[presence.from] = presence; } public void remove_presence(Jid jid) { if (resources.has_key(jid)) { if (jid.is_bare()) { foreach (Jid full_jid in resources[jid]) { presences.unset(full_jid); } resources.unset(jid); } else { resources[jid].remove(jid); if (resources[jid].size == 0) { resources.unset(jid); } presences.unset(jid); } } } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } } dino-0.1.0/xmpp-vala/src/module/presence/module.vala0000644000000000000000000001156313614354364021075 0ustar rootrootusing Gee; namespace Xmpp.Presence { private const string NS_URI = "jabber:client"; public class Module : XmppStreamModule { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "presence_module"); public signal void received_presence(XmppStream stream, Presence.Stanza presence); public signal void pre_send_presence_stanza(XmppStream stream, Presence.Stanza presence); public signal void initial_presence_sent(XmppStream stream, Presence.Stanza presence); public signal void received_available(XmppStream stream, Presence.Stanza presence); public signal void received_available_show(XmppStream stream, Jid jid, string show); public signal void received_unavailable(XmppStream stream, Presence.Stanza presence); public signal void received_subscription_request(XmppStream stream, Jid jid); public signal void received_subscription_approval(XmppStream stream, Jid jid); public signal void received_unsubscription(XmppStream stream, Jid jid); public bool available_resource = true; public void request_subscription(XmppStream stream, Jid bare_jid) { Presence.Stanza presence = new Presence.Stanza(); presence.to = bare_jid; presence.type_ = Presence.Stanza.TYPE_SUBSCRIBE; send_presence(stream, presence); } public void approve_subscription(XmppStream stream, Jid bare_jid) { Presence.Stanza presence = new Presence.Stanza(); presence.to = bare_jid; presence.type_ = Presence.Stanza.TYPE_SUBSCRIBED; send_presence(stream, presence); } public void deny_subscription(XmppStream stream, Jid bare_jid) { cancel_subscription(stream, bare_jid); } public void cancel_subscription(XmppStream stream, Jid bare_jid) { Presence.Stanza presence = new Presence.Stanza(); presence.to = bare_jid; presence.type_ = Presence.Stanza.TYPE_UNSUBSCRIBED; send_presence(stream, presence); } public void unsubscribe(XmppStream stream, Jid bare_jid) { Presence.Stanza presence = new Presence.Stanza(); presence.to = bare_jid; presence.type_ = Presence.Stanza.TYPE_UNSUBSCRIBE; send_presence(stream, presence); } public void send_presence(XmppStream stream, Presence.Stanza presence) { pre_send_presence_stanza(stream, presence); stream.write(presence.stanza); } public override void attach(XmppStream stream) { stream.add_flag(new Flag()); stream.received_presence_stanza.connect(on_received_presence_stanza); stream.stream_negotiated.connect(on_stream_negotiated); } public override void detach(XmppStream stream) { stream.received_presence_stanza.disconnect(on_received_presence_stanza); stream.stream_negotiated.disconnect(on_stream_negotiated); } private void on_received_presence_stanza(XmppStream stream, StanzaNode node) { Presence.Stanza presence = new Presence.Stanza.from_stanza(node, stream.get_flag(Bind.Flag.IDENTITY).my_jid); received_presence(stream, presence); switch (presence.type_) { case Presence.Stanza.TYPE_AVAILABLE: stream.get_flag(Flag.IDENTITY).add_presence(presence); received_available(stream, presence); received_available_show(stream, presence.from, presence.show); break; case Presence.Stanza.TYPE_UNAVAILABLE: stream.get_flag(Flag.IDENTITY).remove_presence(presence.from); received_unavailable(stream, presence); break; case Presence.Stanza.TYPE_SUBSCRIBE: received_subscription_request(stream, presence.from); break; case Presence.Stanza.TYPE_SUBSCRIBED: received_subscription_approval(stream, presence.from); break; case Presence.Stanza.TYPE_UNSUBSCRIBE: stream.get_flag(Flag.IDENTITY).remove_presence(presence.from); received_unsubscription(stream, presence.from); break; case Presence.Stanza.TYPE_UNSUBSCRIBED: break; } } private void on_stream_negotiated(XmppStream stream) { if (available_resource) { Presence.Stanza presence = new Presence.Stanza(); send_presence(stream, presence); initial_presence_sent(stream, presence); } } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } } dino-0.1.0/xmpp-vala/src/module/presence/stanza.vala0000644000000000000000000000566413614354364021115 0ustar rootrootnamespace Xmpp.Presence { public class Stanza : Xmpp.Stanza { public const string NODE_PRIORITY = "priority"; public const string NODE_STATUS = "status"; public const string NODE_SHOW = "show"; public const string SHOW_ONLINE = "online"; public const string SHOW_AWAY = "away"; public const string SHOW_CHAT = "chat"; public const string SHOW_DND = "dnd"; public const string SHOW_XA = "xa"; public const string TYPE_AVAILABLE = "available"; public const string TYPE_PROBE = "probe"; public const string TYPE_SUBSCRIBE = "subscribe"; public const string TYPE_SUBSCRIBED = "subscribed"; public const string TYPE_UNAVAILABLE = "unavailable"; public const string TYPE_UNSUBSCRIBE = "unsubscribe"; public const string TYPE_UNSUBSCRIBED = "unsubscribed"; public int priority { get { StanzaNode? priority_node = stanza.get_subnode(NODE_PRIORITY); if (priority_node == null) { return 0; } else { return int.parse(priority_node.get_string_content()); } } set { StanzaNode? priority_node = stanza.get_subnode(NODE_PRIORITY); if (priority_node == null) { priority_node = new StanzaNode.build(NODE_PRIORITY); stanza.put_node(priority_node); } priority_node.val = value.to_string(); } } public string? status { get { StanzaNode? status_node = stanza.get_subnode(NODE_STATUS); return status_node != null ? status_node.get_string_content() : null; } set { StanzaNode? status_node = stanza.get_subnode(NODE_STATUS); if (status_node == null) { status_node = new StanzaNode.build(NODE_STATUS); stanza.put_node(status_node); } status_node.val = value; } } public string show { get { StanzaNode? show_node = stanza.get_subnode(NODE_SHOW); if (show_node == null) { return SHOW_ONLINE; } return show_node.get_string_content() ?? SHOW_ONLINE; } set { if (value != SHOW_ONLINE) { StanzaNode? show_node = stanza.get_subnode(NODE_SHOW); if (show_node == null) { show_node = new StanzaNode.build(NODE_SHOW); stanza.put_node(show_node); } show_node.val = value; } } } public override string? type_ { get { return base.type_ ?? TYPE_AVAILABLE; } set { base.type_ = value; } } public Stanza(string? id = null) { stanza = new StanzaNode.build("presence"); this.id = id ?? random_uuid(); } public Stanza.from_stanza(StanzaNode stanza_node, Jid my_jid) { base.incoming(stanza_node, my_jid); } } } dino-0.1.0/xmpp-vala/src/module/roster/0000755000000000000000000000000013614354364016447 5ustar rootrootdino-0.1.0/xmpp-vala/src/module/roster/flag.vala0000644000000000000000000000110413614354364020221 0ustar rootrootusing Gee; namespace Xmpp.Roster { public class Flag : XmppStreamFlag { public const string ID = "roster"; public static FlagIdentity IDENTITY = new FlagIdentity(NS_URI, ID); public HashMap roster_items = new HashMap(); public string? iq_id; public Collection get_roster() { return roster_items.values; } public Item? get_item(Jid jid) { return roster_items[jid]; } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } }dino-0.1.0/xmpp-vala/src/module/roster/item.vala0000644000000000000000000000271713614354364020261 0ustar rootrootusing Gee; namespace Xmpp.Roster { public class Item { public const string NODE_JID = "jid"; public const string NODE_NAME = "name"; public const string NODE_SUBSCRIPTION = "subscription"; public const string SUBSCRIPTION_NONE = "none"; public const string SUBSCRIPTION_TO = "to"; public const string SUBSCRIPTION_FROM = "from"; public const string SUBSCRIPTION_BOTH = "both"; public const string SUBSCRIPTION_REMOVE = "remove"; public StanzaNode stanza_node; private Jid jid_; public Jid? jid { get { try { return jid_ ?? (jid_ = new Jid(stanza_node.get_attribute(NODE_JID))); } catch (InvalidJidError e) { warning("Ignoring invalid Jid in roster entry: %s", e.message); return null; } } set { stanza_node.set_attribute(NODE_JID, value.to_string()); } } public string? name { get { return stanza_node.get_attribute(NODE_NAME); } set { if (value != null) stanza_node.set_attribute(NODE_NAME, value); } } public string? subscription { get { return stanza_node.get_attribute(NODE_SUBSCRIPTION); } set { if (value != null) stanza_node.set_attribute(NODE_SUBSCRIPTION, value); } } public Item() { stanza_node = new StanzaNode.build("item", NS_URI); } public Item.from_stanza_node(StanzaNode stanza_node) { this.stanza_node = stanza_node; } } } dino-0.1.0/xmpp-vala/src/module/roster/module.vala0000644000000000000000000001123413614354364020602 0ustar rootrootusing Gee; namespace Xmpp.Roster { private const string NS_URI = "jabber:iq:roster"; public class Module : XmppStreamModule, Iq.Handler { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "roster_module"); public signal void received_roster(XmppStream stream, Collection roster, Iq.Stanza stanza); public signal void pre_get_roster(XmppStream stream, Iq.Stanza iq); public signal void item_removed(XmppStream stream, Item item, Iq.Stanza iq); public signal void item_updated(XmppStream stream, Item item, Iq.Stanza iq); public signal void mutual_subscription(XmppStream stream, Jid jid); public bool interested_resource = true; public void add_jid(XmppStream stream, Jid jid, string? handle = null) { Item roster_item = new Item(); roster_item.jid = jid; if (handle != null) { roster_item.name = handle; } roster_set(stream, roster_item); } public void remove_jid(XmppStream stream, Jid jid) { Item roster_item = new Item(); roster_item.jid = jid; roster_item.subscription = Item.SUBSCRIPTION_REMOVE; roster_set(stream, roster_item); } /** * Set a handle for a jid * @param handle Handle to be set. If null, any handle will be removed. */ public void set_jid_handle(XmppStream stream, Jid jid, string? handle) { Flag flag = stream.get_flag(Flag.IDENTITY); Item item = flag.get_item(jid) ?? new Item() { jid=jid }; item.name = handle != null ? handle : ""; roster_set(stream, item); } public void on_iq_set(XmppStream stream, Iq.Stanza iq) { StanzaNode? query_node = iq.stanza.get_subnode("query", NS_URI); if (query_node == null) return; if (!iq.from.equals(stream.get_flag(Bind.Flag.IDENTITY).my_jid.bare_jid)) { warning("Received alleged roster push from %s, ignoring", iq.from.to_string()); return; } Flag flag = stream.get_flag(Flag.IDENTITY); Item item = new Item.from_stanza_node(query_node.get_subnode("item", NS_URI)); switch (item.subscription) { case Item.SUBSCRIPTION_REMOVE: flag.roster_items.unset(item.jid); item_removed(stream, item, iq); break; default: bool is_new = false; Item old = flag.get_item(item.jid); is_new = item.subscription == Item.SUBSCRIPTION_BOTH && (old == null || old.subscription == Item.SUBSCRIPTION_BOTH); flag.roster_items[item.jid] = item; item_updated(stream, item, iq); if(is_new) mutual_subscription(stream, item.jid); break; } } public override void attach(XmppStream stream) { stream.get_module(Iq.Module.IDENTITY).register_for_namespace(NS_URI, this); stream.get_module(Presence.Module.IDENTITY).initial_presence_sent.connect(roster_get); stream.add_flag(new Flag()); } public override void detach(XmppStream stream) { stream.get_module(Presence.Module.IDENTITY).initial_presence_sent.disconnect(roster_get); } internal override string get_ns() { return NS_URI; } internal override string get_id() { return IDENTITY.id; } private void roster_get(XmppStream stream) { stream.get_flag(Flag.IDENTITY).iq_id = random_uuid(); StanzaNode query_node = new StanzaNode.build("query", NS_URI).add_self_xmlns(); Iq.Stanza iq = new Iq.Stanza.get(query_node, stream.get_flag(Flag.IDENTITY).iq_id); pre_get_roster(stream, iq); stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq, on_roster_get_received); } private static void on_roster_get_received(XmppStream stream, Iq.Stanza iq) { Flag flag = stream.get_flag(Flag.IDENTITY); if (iq.id == flag.iq_id) { StanzaNode? query_node = iq.stanza.get_subnode("query", NS_URI); if (query_node != null) { foreach (StanzaNode item_node in query_node.sub_nodes) { Item item = new Item.from_stanza_node(item_node); flag.roster_items[item.jid] = item; } } stream.get_module(Module.IDENTITY).received_roster(stream, flag.roster_items.values, iq); } } private void roster_set(XmppStream stream, Item roster_item) { StanzaNode query_node = new StanzaNode.build("query", NS_URI).add_self_xmlns() .put_node(roster_item.stanza_node); Iq.Stanza iq = new Iq.Stanza.set(query_node); stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq); } } } dino-0.1.0/xmpp-vala/src/module/roster/versioning_module.vala0000644000000000000000000000544213614354364023051 0ustar rootrootusing Gee; namespace Xmpp.Roster { public class VersioningModule : XmppStreamModule { private const string NS_URI_FEATURE = "urn:xmpp:features:rosterver"; public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "roster_versioning"); private Storage storage; public VersioningModule(Storage storage) { this.storage = storage; } public override void attach(XmppStream stream) { stream.get_module(Module.IDENTITY).pre_get_roster.connect(on_pre_get_roster); stream.get_module(Module.IDENTITY).received_roster.connect(on_received_roster); stream.get_module(Module.IDENTITY).item_updated.connect(on_item_updated); stream.get_module(Module.IDENTITY).item_removed.connect(on_item_removed); } public override void detach(XmppStream stream) { stream.get_module(Module.IDENTITY).pre_get_roster.disconnect(on_pre_get_roster); } internal override string get_ns() { return NS_URI; } internal override string get_id() { return IDENTITY.id; } private void on_pre_get_roster(XmppStream stream, Iq.Stanza iq) { StanzaNode? ver_feature = stream.features.get_subnode("ver", NS_URI_FEATURE); if (ver_feature != null) { iq.stanza.get_subnode("query", NS_URI).set_attribute("ver", storage.get_roster_version() ?? ""); } } private void on_received_roster(XmppStream stream, Collection roster, Iq.Stanza iq) { string? ver = iq.stanza.get_deep_attribute(NS_URI + ":query", NS_URI + ":ver"); if (ver != null) storage.set_roster_version(ver); if (iq.stanza.get_subnode("query", NS_URI) != null) { storage.set_roster(roster); } else { Flag flag = stream.get_flag(Flag.IDENTITY); foreach (Item item in storage.get_roster()) { flag.roster_items[item.jid] = item; } } } private void on_item_updated(XmppStream stream, Item item, Iq.Stanza iq) { string? ver = iq.stanza.get_deep_attribute(NS_URI + ":query", NS_URI + ":ver"); if (ver != null) storage.set_roster_version(ver); storage.set_item(item); } private void on_item_removed(XmppStream stream, Item item, Iq.Stanza iq) { string? ver = iq.stanza.get_deep_attribute(NS_URI + ":query", NS_URI + ":ver"); if (ver != null) storage.set_roster_version(ver); storage.remove_item(item); } } public interface Storage : Object { public abstract string? get_roster_version(); public abstract Collection get_roster(); public abstract void set_roster_version(string version); public abstract void set_roster(Collection items); public abstract void set_item(Roster.Item item); public abstract void remove_item(Roster.Item item); } } dino-0.1.0/xmpp-vala/src/module/sasl.vala0000644000000000000000000002601613614354364016745 0ustar rootrootnamespace Xmpp.Sasl { private const string NS_URI = "urn:ietf:params:xml:ns:xmpp-sasl"; public class Flag : XmppStreamFlag { public static FlagIdentity IDENTITY = new FlagIdentity(NS_URI, "sasl"); public string mechanism; public string name; public string password; public string client_nonce; public uint8[] server_signature; public bool finished = false; public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } namespace Mechanism { public const string PLAIN = "PLAIN"; public const string SCRAM_SHA_1 = "SCRAM-SHA-1"; public const string SCRAM_SHA_1_PLUS = "SCRAM-SHA-1-PLUS"; } public class Module : XmppStreamNegotiationModule { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "sasl"); public string name { get; set; } public string password { get; set; } public bool use_full_name = false; public signal void received_auth_failure(XmppStream stream, StanzaNode node); public Module(string name, string password) { this.name = name; this.password = password; } public override void attach(XmppStream stream) { stream.received_features_node.connect(this.received_features_node); stream.received_nonza.connect(this.received_nonza); } public override void detach(XmppStream stream) { stream.received_features_node.disconnect(this.received_features_node); stream.received_nonza.disconnect(this.received_nonza); } private static size_t SHA1_SIZE = 20; private static uint8[] sha1(uint8[] data) { Checksum checksum = new Checksum(ChecksumType.SHA1); checksum.update(data, data.length); uint8[] res = new uint8[SHA1_SIZE]; checksum.get_digest(res, ref SHA1_SIZE); return res; } private static uint8[] hmac_sha1(uint8[] key, uint8[] data) { Hmac hmac = new Hmac(ChecksumType.SHA1, key); hmac.update(data); uint8[] res = new uint8[SHA1_SIZE]; hmac.get_digest(res, ref SHA1_SIZE); return res; } private static uint8[] pbkdf2_sha1(string password, uint8[] salt, uint iterations) { uint8[] res = new uint8[SHA1_SIZE]; uint8[] last = new uint8[salt.length + 4]; for(int i = 0; i < salt.length; i++) { last[i] = salt[i]; } last[salt.length + 3] = 1; for(int i = 0; i < iterations; i++) { last = hmac_sha1((uint8[]) password.to_utf8(), last); xor_inplace(res, last); } return res; } private static void xor_inplace(uint8[] mix, uint8[] a2) { for(int i = 0; i < mix.length; i++) { mix[i] = mix[i] ^ a2[i]; } } private static uint8[] xor(uint8[] a1, uint8[] a2) { uint8[] mix = new uint8[a1.length]; for(int i = 0; i < a1.length; i++) { mix[i] = a1[i] ^ a2[i]; } return mix; } public void received_nonza(XmppStream stream, StanzaNode node) { if (node.ns_uri == NS_URI) { if (node.name == "success") { Flag flag = stream.get_flag(Flag.IDENTITY); if (flag.mechanism == Mechanism.SCRAM_SHA_1) { string confirm = (string) Base64.decode(node.get_string_content()); uint8[] server_signature = null; foreach(string c in confirm.split(",")) { string[] split = c.split("=", 2); if (split.length != 2) continue; switch(split[0]) { case "v": server_signature = Base64.decode(split[1]); break; } } if (server_signature == null) return; if (server_signature.length != flag.server_signature.length) return; for(int i = 0; i < server_signature.length; i++) { if (server_signature[i] != flag.server_signature[i]) return; } } stream.require_setup(); flag.password = null; // Remove password from memory flag.finished = true; } else if (node.name == "failure") { stream.remove_flag(stream.get_flag(Flag.IDENTITY)); received_auth_failure(stream, node); } else if (node.name == "challenge" && stream.has_flag(Flag.IDENTITY)) { Flag flag = stream.get_flag(Flag.IDENTITY); if (flag.mechanism == Mechanism.SCRAM_SHA_1) { string challenge = (string) Base64.decode(node.get_string_content()); string? server_nonce = null; uint8[] salt = null; uint iterations = 0; foreach(string c in challenge.split(",")) { string[] split = c.split("=", 2); if (split.length != 2) continue; switch(split[0]) { case "r": server_nonce = split[1]; break; case "s": salt = Base64.decode(split[1]); break; case "i": iterations = int.parse(split[1]); break; } } if (server_nonce == null || salt == null || iterations == 0) return; if (!server_nonce.has_prefix(flag.client_nonce)) return; string client_final_message_bare = @"c=biws,r=$server_nonce"; uint8[] salted_password = pbkdf2_sha1(flag.password, salt, iterations); uint8[] client_key = hmac_sha1(salted_password, (uint8[]) "Client Key".to_utf8()); uint8[] stored_key = sha1(client_key); string auth_message = @"n=$(flag.name),r=$(flag.client_nonce),$challenge,$client_final_message_bare"; uint8[] client_signature = hmac_sha1(stored_key, (uint8[]) auth_message.to_utf8()); uint8[] client_proof = xor(client_key, client_signature); uint8[] server_key = hmac_sha1(salted_password, (uint8[]) "Server Key".to_utf8()); flag.server_signature = hmac_sha1(server_key, (uint8[]) auth_message.to_utf8()); string client_final_message = @"$client_final_message_bare,p=$(Base64.encode(client_proof))"; stream.write(new StanzaNode.build("response", NS_URI).add_self_xmlns() .put_node(new StanzaNode.text(Base64.encode((uchar[]) (client_final_message).to_utf8())))); } } } } public void received_features_node(XmppStream stream) { if (stream.has_flag(Flag.IDENTITY)) return; if (stream.is_setup_needed()) return; if (!stream.has_flag(Tls.Flag.IDENTITY) || !stream.get_flag(Tls.Flag.IDENTITY).finished) return; var mechanisms = stream.features.get_subnode("mechanisms", NS_URI); string[] supported_mechanisms = {}; foreach (var mechanism in mechanisms.sub_nodes) { if (mechanism.name != "mechanism" || mechanism.ns_uri != NS_URI) continue; supported_mechanisms += mechanism.get_string_content(); } if (!name.contains("@")) { name = "%s@%s".printf(name, stream.remote_name.to_string()); } if (!use_full_name && name.contains("@")) { var split = name.split("@"); if (split[1] == stream.remote_name.to_string()) { name = split[0]; } else { use_full_name = true; } } string name = this.name; if (!use_full_name && name.contains("@")) { var split = name.split("@"); if (split[1] == stream.remote_name.to_string()) { name = split[0]; } } if (Mechanism.SCRAM_SHA_1 in supported_mechanisms) { string normalized_password = password.normalize(-1, NormalizeMode.NFKC); string client_nonce = Random.next_int().to_string("%.8x") + Random.next_int().to_string("%.8x") + Random.next_int().to_string("%.8x"); string initial_message = @"n=$name,r=$client_nonce"; stream.write(new StanzaNode.build("auth", NS_URI).add_self_xmlns() .put_attribute("mechanism", Mechanism.SCRAM_SHA_1) .put_node(new StanzaNode.text(Base64.encode((uchar[]) ("n,,"+initial_message).to_utf8())))); var flag = new Flag(); flag.mechanism = Mechanism.SCRAM_SHA_1; flag.name = name; flag.password = normalized_password; flag.client_nonce = client_nonce; stream.add_flag(flag); } else if (Mechanism.PLAIN in supported_mechanisms) { stream.write(new StanzaNode.build("auth", NS_URI).add_self_xmlns() .put_attribute("mechanism", Mechanism.PLAIN) .put_node(new StanzaNode.text(Base64.encode(get_plain_bytes(name, password))))); var flag = new Flag(); flag.mechanism = Mechanism.PLAIN; flag.name = name; stream.add_flag(flag); } else { stderr.printf("No supported mechanism provided by server at %s\n", stream.remote_name.to_string()); return; } } private static uchar[] get_plain_bytes(string name_s, string password_s) { var name = name_s.to_utf8(); var password = password_s.to_utf8(); uchar[] res = new uchar[name.length + password.length + 2]; res[0] = 0; res[name.length + 1] = 0; for(int i = 0; i < name.length; i++) { res[i + 1] = (uchar) name[i]; } for(int i = 0; i < password.length; i++) { res[i + name.length + 2] = (uchar) password[i]; } return res; } public override bool mandatory_outstanding(XmppStream stream) { return !stream.has_flag(Flag.IDENTITY) || !stream.get_flag(Flag.IDENTITY).finished; } public override bool negotiation_active(XmppStream stream) { return stream.has_flag(Flag.IDENTITY) && !stream.get_flag(Flag.IDENTITY).finished; } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } } dino-0.1.0/xmpp-vala/src/module/session.vala0000644000000000000000000000354213614354364017465 0ustar rootroot/* Legacy. RFC 3921 3*/ namespace Xmpp.Session { private const string NS_URI = "urn:ietf:params:xml:ns:xmpp-session"; public class Module : XmppStreamNegotiationModule { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "session"); public override void attach(XmppStream stream) { stream.get_module(Bind.Module.IDENTITY).bound_to_resource.connect(on_bound_resource); } public override void detach(XmppStream stream) { stream.get_module(Bind.Module.IDENTITY).bound_to_resource.disconnect(on_bound_resource); } public override bool mandatory_outstanding(XmppStream stream) { return false; } public override bool negotiation_active(XmppStream stream) { return stream.has_flag(Flag.IDENTITY) && !stream.get_flag(Flag.IDENTITY).finished; } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } private void on_bound_resource(XmppStream stream, Jid my_jid) { StanzaNode? session_node = stream.features.get_subnode("session", NS_URI); if (session_node != null && session_node.get_subnode("optional", NS_URI) == null) { stream.add_flag(new Flag()); Iq.Stanza iq = new Iq.Stanza.set(new StanzaNode.build("session", NS_URI).add_self_xmlns()) { to=stream.remote_name }; stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq, (stream, iq) => { if (!iq.is_error()) { stream.get_flag(Flag.IDENTITY).finished = true; } }); } } } public class Flag : XmppStreamFlag { public static FlagIdentity IDENTITY = new FlagIdentity(NS_URI, "session"); public bool finished = false; public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } } dino-0.1.0/xmpp-vala/src/module/stanza.vala0000644000000000000000000000500313614354364017274 0ustar rootrootnamespace Xmpp { public class Stanza : Object { public const string ATTRIBUTE_FROM = "from"; public const string ATTRIBUTE_ID = "id"; public const string ATTRIBUTE_TO = "to"; public const string ATTRIBUTE_TYPE = "type"; public const string TYPE_ERROR = "error"; private Jid? my_jid; private Jid? from_; private Jid? to_; public virtual Jid? from { owned get { string? from_attribute = stanza.get_attribute(ATTRIBUTE_FROM); // "when a client receives a stanza that does not include a 'from' attribute, it MUST assume that the stanza // is from the user's account on the server." (RFC6120 8.1.2.1) if (from_attribute != null) { try { return from_ = new Jid(from_attribute); } catch (InvalidJidError e) { warning("Ignoring invalid from Jid: %s", e.message); } } if (my_jid != null) { return my_jid.bare_jid; } return null; } set { stanza.set_attribute(ATTRIBUTE_FROM, value.to_string()); } } public virtual string? id { get { return stanza.get_attribute(ATTRIBUTE_ID); } set { stanza.set_attribute(ATTRIBUTE_ID, value); } } public virtual Jid? to { owned get { string? to_attribute = stanza.get_attribute(ATTRIBUTE_TO); // "if the stanza does not include a 'to' address then the client MUST treat it as if the 'to' address were // included with a value of the client's full JID." (RFC6120 8.1.1.1) try { return to_attribute == null ? my_jid : to_ = new Jid(to_attribute); } catch (InvalidJidError e) { warning("Ignoring invalid to Jid: %s", e.message); } return my_jid; } set { stanza.set_attribute(ATTRIBUTE_TO, value.to_string()); } } public virtual string? type_ { get { return stanza.get_attribute(ATTRIBUTE_TYPE); } set { stanza.set_attribute(ATTRIBUTE_TYPE, value); } } public StanzaNode stanza; public Stanza.incoming(StanzaNode stanza, Jid? my_jid) { this.stanza = stanza; this.my_jid = my_jid; } public Stanza.outgoing(StanzaNode stanza) { this.stanza = stanza; } public bool is_error() { return type_ == TYPE_ERROR; } public ErrorStanza? get_error() { return new ErrorStanza.from_stanza(this.stanza); } } }dino-0.1.0/xmpp-vala/src/module/stanza_error.vala0000644000000000000000000001156413614354364020516 0ustar rootrootusing Gee; namespace Xmpp { private const string ERROR_NS_URI = "urn:ietf:params:xml:ns:xmpp-stanzas"; public class ErrorStanza { public const string CONDITION_BAD_REQUEST = "bad-request"; public const string CONDITION_CONFLICT = "conflict"; public const string CONDITION_FEATURE_NOT_IMPLEMENTED = "feature-not-implemented"; public const string CONDITION_FORBIDDEN = "forbidden"; public const string CONDITION_GONE = "gone"; public const string CONDITION_INTERNAL_SERVER_ERROR = "internal-server-error"; public const string CONDITION_ITEM_NOT_FOUND = "item-not-found"; public const string CONDITION_JID_MALFORMED = "jid-malformed"; public const string CONDITION_NOT_ACCEPTABLE = "not-acceptable"; public const string CONDITION_NOT_ALLOWED = "not-allowed"; public const string CONDITION_NOT_AUTHORIZED = "not-authorized"; public const string CONDITION_POLICY_VIOLATION = "policy-violation"; public const string CONDITION_RECIPIENT_UNAVAILABLE = "recipient-unavailable"; public const string CONDITION_REDIRECT = "redirect"; public const string CONDITION_REGISTRATION_REQUIRED = "registration-required"; public const string CONDITION_REMOTE_SERVER_NOT_FOUND = "remote-server-not-found"; public const string CONDITION_REMOTE_SERVER_TIMEOUT = "remote-server-timeout"; public const string CONDITION_RESOURCE_CONSTRAINT = "resource-constraint"; public const string CONDITION_SERVICE_UNAVAILABLE = "service-unavailable"; public const string CONDITION_SUBSCRIPTION_REQUIRED = "subscription-required"; public const string CONDITION_UNDEFINED_CONDITION = "undefined-condition"; public const string CONDITION_UNEXPECTED_REQUEST = "unexpected-request"; public const string TYPE_AUTH = "auth"; public const string TYPE_CANCEL = "cancel"; public const string TYPE_CONTINUE = "continue"; public const string TYPE_MODIFY = "modify"; public const string TYPE_WAIT = "wait"; public string? by { get { return error_node.get_attribute("by"); } } public string? text { get { return error_node.get_deep_string_content(ERROR_NS_URI + ":text"); } } public string condition { get { Gee.List subnodes = error_node.sub_nodes; foreach (StanzaNode subnode in subnodes) { // TODO get subnode by ns if (subnode.ns_uri == ERROR_NS_URI) { return subnode.name; } } return CONDITION_UNDEFINED_CONDITION; // TODO hm! } } public string type_ { get { return error_node.get_attribute("type"); } } public StanzaNode error_node; public ErrorStanza.from_stanza(StanzaNode stanza) { error_node = stanza.get_subnode("error"); } public ErrorStanza.build(string type, string condition, string? human_readable, StanzaNode? application_condition) { error_node = new StanzaNode.build("error") .put_attribute("type", type) .put_node(new StanzaNode.build(condition, ERROR_NS_URI).add_self_xmlns()); if (application_condition != null) { error_node.put_node(application_condition); } if (human_readable != null) { error_node.put_node(new StanzaNode.build("text", ERROR_NS_URI) .add_self_xmlns() .put_attribute("xml:lang", "en") .put_node(new StanzaNode.text(human_readable)) ); } } public ErrorStanza.bad_request(string? human_readable = null) { this.build(TYPE_MODIFY, CONDITION_BAD_REQUEST, human_readable, null); } public ErrorStanza.feature_not_implemented(string? human_readable = null) { this.build(TYPE_MODIFY, CONDITION_FEATURE_NOT_IMPLEMENTED, human_readable, null); } public ErrorStanza.item_not_found(StanzaNode? application_condition = null) { this.build(TYPE_CANCEL, CONDITION_ITEM_NOT_FOUND, null, application_condition); } public ErrorStanza.not_acceptable(string? human_readable = null) { this.build(TYPE_MODIFY, CONDITION_NOT_ACCEPTABLE, human_readable, null); } public ErrorStanza.not_allowed(string? human_readable = null) { this.build(TYPE_CANCEL, CONDITION_NOT_ALLOWED, human_readable, null); } public ErrorStanza.resource_constraint(string? human_readable = null) { this.build(TYPE_WAIT, CONDITION_RESOURCE_CONSTRAINT, human_readable, null); } public ErrorStanza.service_unavailable() { this.build(TYPE_CANCEL, CONDITION_SERVICE_UNAVAILABLE, null, null); } } } dino-0.1.0/xmpp-vala/src/module/stream_error.vala0000644000000000000000000001013213614354364020477 0ustar rootrootusing Gee; namespace Xmpp.StreamError { private const string NS_URI = "jabber:client"; private const string NS_ERROR = "urn:ietf:params:xml:ns:xmpp-streams"; public class Module : XmppStreamModule { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "stream_error_module"); public override void attach(XmppStream stream) { stream.received_nonza.connect(on_received_nonstanza); } public override void detach(XmppStream stream) { stream.received_nonza.disconnect(on_received_nonstanza); } public static void require(XmppStream stream) { if (stream.get_module(IDENTITY) == null) stream.add_module(new Module()); } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } private void on_received_nonstanza(XmppStream stream, StanzaNode node) { if (node.name == "error" && node.ns_uri == "http://etherx.jabber.org/streams") { stream.add_flag(generate_error_flag(node)); } } private Flag generate_error_flag(StanzaNode node) { string? subnode_name = null; Gee.List subnodes = node.sub_nodes; foreach (StanzaNode subnode in subnodes) { // TODO get subnode by ns if (subnode.ns_uri == "urn:ietf:params:xml:ns:xmpp-streams" && subnode.name != "text") { subnode_name = subnode.name; } } Flag flag = new StreamError.Flag(); flag.error_type = subnode_name; switch (subnode_name) { case "bad-format": case "conflict": case "connection-timeout": case "bad-namespace-prefix": flag.reconnection_recomendation = StreamError.Flag.Reconnect.NOW; break; case "host-gone": case "host-unknown": flag.reconnection_recomendation = StreamError.Flag.Reconnect.LATER; break; case "improper-addressing": case "internal-server-error": case "invalid-from": case "invalid-namespace": case "invalid-xml": case "not-authorized": case "not-well-formed": case "policy-violation": case "remote-connection-failed": case "reset": flag.reconnection_recomendation = StreamError.Flag.Reconnect.NOW; break; case "resource-constraint": flag.reconnection_recomendation = StreamError.Flag.Reconnect.LATER; break; case "restricted-xml": flag.reconnection_recomendation = StreamError.Flag.Reconnect.NOW; break; case "see-other-host": case "system-shutdown": flag.reconnection_recomendation = StreamError.Flag.Reconnect.LATER; break; case "undefined-condition": case "unsupported-encoding": case "unsupported-feature": case "unsupported-stanza-type": case "unsupported-version": flag.reconnection_recomendation = StreamError.Flag.Reconnect.NOW; break; } if (subnode_name == "conflict") flag.resource_rejected = true; return flag; } } public class Flag : XmppStreamFlag { public static FlagIdentity IDENTITY = new FlagIdentity(NS_URI, "stream_error"); public enum Reconnect { UNKNOWN, NOW, LATER, NEVER } public string? error_type; public Reconnect reconnection_recomendation = Reconnect.UNKNOWN; public bool resource_rejected = false; public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } } dino-0.1.0/xmpp-vala/src/module/tls.vala0000644000000000000000000001034413614354364016602 0ustar rootrootnamespace Xmpp.Tls { private const string NS_URI = "urn:ietf:params:xml:ns:xmpp-tls"; public class Module : XmppStreamNegotiationModule { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "tls_module"); public signal void invalid_certificate(TlsCertificate peer_cert, TlsCertificateFlags errors); public bool require { get; set; default = true; } public bool server_supports_tls = false; public bool server_requires_tls = false; public SocketConnectable? identity = null; public override void attach(XmppStream stream) { stream.received_features_node.connect(this.received_features_node); stream.received_nonza.connect(this.received_nonza); } public override void detach(XmppStream stream) { stream.received_features_node.disconnect(this.received_features_node); stream.received_nonza.disconnect(this.received_nonza); } private void received_nonza(XmppStream stream, StanzaNode node) { if (node.ns_uri == NS_URI && node.name == "proceed") { try { var io_stream = stream.get_stream(); if (io_stream == null) return; var conn = TlsClientConnection.new(io_stream, identity); stream.reset_stream(conn); conn.accept_certificate.connect(on_invalid_certificate); var flag = stream.get_flag(Flag.IDENTITY); flag.peer_certificate = conn.get_peer_certificate(); flag.finished = true; } catch (Error e) { stderr.printf("Failed to start TLS: %s\n", e.message); } } } private void received_features_node(XmppStream stream) { if (stream.has_flag(Flag.IDENTITY)) return; if (stream.is_setup_needed()) return; var starttls = stream.features.get_subnode("starttls", NS_URI); if (starttls != null) { server_supports_tls = true; if (starttls.get_subnode("required") != null || stream.features.get_all_subnodes().size == 1) { server_requires_tls = true; } if (server_requires_tls || require) { stream.write(new StanzaNode.build("starttls", NS_URI).add_self_xmlns()); } if (identity == null) { identity = new NetworkService("xmpp-client", "tcp", stream.remote_name.to_string()); } stream.add_flag(new Flag()); } } public bool on_invalid_certificate(TlsCertificate peer_cert, TlsCertificateFlags errors) { string error_str = ""; foreach (var f in new TlsCertificateFlags[]{TlsCertificateFlags.UNKNOWN_CA, TlsCertificateFlags.BAD_IDENTITY, TlsCertificateFlags.NOT_ACTIVATED, TlsCertificateFlags.EXPIRED, TlsCertificateFlags.REVOKED, TlsCertificateFlags.INSECURE, TlsCertificateFlags.GENERIC_ERROR, TlsCertificateFlags.VALIDATE_ALL}) { if (f in errors) { error_str += @"$(f), "; } } warning(@"Tls Certificate Errors: $(error_str)"); invalid_certificate(peer_cert, errors); return false; } public override bool mandatory_outstanding(XmppStream stream) { return require && (!stream.has_flag(Flag.IDENTITY) || !stream.get_flag(Flag.IDENTITY).finished); } public override bool negotiation_active(XmppStream stream) { return stream.has_flag(Flag.IDENTITY) && !stream.get_flag(Flag.IDENTITY).finished; } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } public class Flag : XmppStreamFlag { public static FlagIdentity IDENTITY = new FlagIdentity(NS_URI, "tls"); public TlsCertificate? peer_certificate; public bool finished { get; set; default=false; } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } } dino-0.1.0/xmpp-vala/src/module/util.vala0000644000000000000000000000523713614354364016762 0ustar rootrootusing Gee; namespace Xmpp { public string random_uuid() { uint32 b1 = Random.next_int(); uint16 b2 = (uint16)Random.next_int(); uint16 b3 = (uint16)(Random.next_int() | 0x4000u) & ~0xb000u; uint16 b4 = (uint16)(Random.next_int() | 0x8000u) & ~0x4000u; uint16 b5_1 = (uint16)Random.next_int(); uint32 b5_2 = Random.next_int(); return "%08x-%04x-%04x-%04x-%04x%08x".printf(b1, b2, b3, b4, b5_1, b5_2); } public abstract class StanzaListener : OrderedListener { public abstract async bool run(XmppStream stream, T stanza); } public class StanzaListenerHolder : ListenerHolder { public async bool run(XmppStream stream, T stanza) { // listeners can change e.g. when switching to another stream ArrayList listeners_copy = new ArrayList(); listeners_copy.add_all(listeners); foreach (OrderedListener ol in listeners_copy) { StanzaListener l = ol as StanzaListener; bool stop = yield l.run(stream, stanza); if (stop) return true; } return false; } } public abstract class OrderedListener : Object { public abstract string action_group { get; } public abstract string[] after_actions { get; } } public abstract class ListenerHolder : Object { protected ArrayList listeners = new ArrayList(); public new void connect(OrderedListener listener) { listeners.add(listener); resort_list(); } public new void disconnect(OrderedListener listener) { listeners.remove(listener); resort_list(); } private bool set_contains_action(Gee.List s, string[] actions) { foreach(OrderedListener l in s) { if (l.action_group in actions) { return true; } } return false; } private void resort_list() { ArrayList new_list = new ArrayList(); ArrayList remaining = new ArrayList(); remaining.add_all(listeners); while (remaining.size > 0) { bool changed = false; Gee.Iterator iter = remaining.iterator(); while (iter.has_next()) { iter.next(); OrderedListener l = iter.get(); if (!set_contains_action(remaining, l.after_actions)) { new_list.add(l); iter.remove(); changed = true; } } if (!changed) error("Can't sort listeners"); } listeners = new_list; } } } dino-0.1.0/xmpp-vala/src/module/xep/0000755000000000000000000000000013614354364015725 5ustar rootrootdino-0.1.0/xmpp-vala/src/module/xep/0004_data_forms.vala0000644000000000000000000001636413614354364021366 0ustar rootrootusing Gee; namespace Xmpp.Xep.DataForms { public const string NS_URI = "jabber:x:data"; public class DataForm { public StanzaNode stanza_node { get; set; } public Gee.List fields = new ArrayList(); public string? form_type = null; public StanzaNode get_submit_node() { stanza_node.set_attribute("type", "submit"); return stanza_node; } public enum Type { BOOLEAN, FIXED, HIDDEN, JID_MULTI, LIST_SINGLE, LIST_MULTI, TEXT_PRIVATE, TEXT_SINGLE, } public class Option { public string label { get; set; } public string value { get; set; } public Option(string label, string value) { this.label = label; this.value = value; } } public class Field { public StanzaNode node { get; set; } public string? label { get { return node.get_attribute("label", NS_URI); } set { node.set_attribute("label", value); } } public virtual Type? type_ { get; internal set; default=null; } public string? var { get { return node.get_attribute("var", NS_URI); } set { node.set_attribute("var", value); } } public Field() { this.node = new StanzaNode.build("field", NS_URI); } public Field.from_node(StanzaNode node) { this.node = node; } internal Gee.List get_values() { Gee.List ret = new ArrayList(); Gee.List value_nodes = node.get_subnodes("value", NS_URI); foreach (StanzaNode node in value_nodes) { ret.add(node.get_string_content()); } return ret; } public string get_value_string() { Gee.List values = get_values(); return values.size > 0 ? values[0] : ""; } public void set_value_string(string val) { StanzaNode? value_node = node.get_subnode("value", NS_URI); if (value_node == null) { value_node = new StanzaNode.build("value", NS_URI); node.put_node(value_node); } value_node.sub_nodes.clear(); value_node.put_node(new StanzaNode.text(val)); } internal void add_value_string(string val) { StanzaNode node = new StanzaNode.build("value"); node.put_node(new StanzaNode.text(val)); } internal Gee.List